openapi: 3.1.0
info:
  title: Notifications Workers API
  description: |
    ## Overview
    API for Decentraland's notification system, consisting of two services:

    **Inbox Worker**: Manages user notifications, subscription preferences, and email verification.

    **Processor Worker**: Handles notification creation and publishing from external sources.

    The services share a database and support both in-app and email notifications with granular user preferences per notification type.
  version: 1.0.0
  contact:
    name: Decentraland
    url: https://decentraland.org
  license:
    name: Apache 2.0
    url: https://www.apache.org/licenses/LICENSE-2.0.html
  x-api-id: notifications-workers-api
servers:
  - url: https://notifications.decentraland.org
    description: Production environment
  - url: https://notifications.decentraland.zone
    description: Development environment
security: []
tags:
  - name: Health
    description: Service health and status endpoints (available in both Inbox and Processor services)
  - name: Notifications
    description: User notification retrieval and management (Inbox service)
  - name: Subscriptions
    description: User subscription preferences and email verification (Inbox service)
  - name: Publishing
    description: External notification creation and publishing (Processor service)
paths:
  /status:
    get:
      summary: Get service health status
      description: Returns health status of the service
      operationId: getStatus
      tags:
        - Health
      security: []
      responses:
        '200':
          description: Service health status
          content:
            application/json:
              schema:
                type: object
                properties: &ref_7
                  status:
                    type: string
                    description: Service health status
                    example: ok
                  timestamp:
                    type: string
                    format: date-time
                    description: Status timestamp
                required: &ref_8
                  - status
  /notifications:
    get:
      summary: Get user notifications
      description: |
        Retrieves paginated notifications for the authenticated user, including both user-specific and broadcast notifications.
        The user's address is extracted from the signed fetch authentication chain.
      operationId: getNotifications
      tags:
        - Notifications
      security:
        - SignedFetch: []
      parameters:
        - name: from
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
          description: Unix timestamp in milliseconds. Only return notifications with timestamp >= this value
          example: 1701379983000
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 20
          description: Maximum number of notifications to return (capped at 50)
        - name: onlyUnread
          in: query
          required: false
          schema:
            type: boolean
          description: If present (value doesn't matter), only unread notifications are returned
      responses:
        '200':
          description: User notifications retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties: &ref_9
                  notifications:
                    type: array
                    items:
                      type: object
                      properties: &ref_12
                        id:
                          type: string
                          format: uuid
                          description: Unique notification identifier
                        type:
                          type: string
                          description: Notification type (from NotificationType enum)
                          example: bid_accepted
                        address:
                          oneOf:
                            - type: string
                              pattern: ^0x[a-fA-F0-9]{40}$
                            - type: 'null'
                          description: User's Ethereum address (null for broadcast notifications)
                        metadata:
                          type: object
                          description: Notification-specific metadata containing details like links, titles, descriptions, etc.
                          additionalProperties: true
                        timestamp:
                          type: string
                          description: Unix timestamp (in seconds) when the notification event occurred
                          example: '1701379983'
                        read:
                          type: boolean
                          description: Whether the notification has been marked as read by the user
                      required: &ref_13
                        - id
                        - type
                        - address
                        - metadata
                        - timestamp
                        - read
                    description: List of notifications for the user
                required: &ref_10
                  - notifications
                example: &ref_11
                  notifications:
                    - id: 3a33a38a-e17a-4153-926b-c18f1e57e3e1
                      type: bid_accepted
                      address: '0xb5D7D1A05f553b5098D9274Df6B292e4e8222314'
                      metadata:
                        link: https://decentraland.zone/marketplace/contracts/0x5d670bab052f21c3b1984231b1187be34852db24/tokens/105312291668557186697918027683670432318895095400549111254310977537
                        title: Bid Accepted
                      timestamp: '1701379983'
                      read: false
        '401':
          description: Unauthorized - invalid or missing signed fetch headers
          content:
            application/json:
              schema:
                type: object
                properties: &ref_0
                  error:
                    type: string
                    description: Error type or category
                    example: Bad request
                  message:
                    type: string
                    description: Detailed error message
                    example: Missing notificationIds
                  code:
                    type: string
                    description: Optional error code for programmatic error handling
                required: &ref_1
                  - error
        '403':
          description: Forbidden - signed fetch metadata validation failed (e.g., request from decentraland-kernel-scene)
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
    post:
      summary: Publish notifications
      description: |
        Creates and stores new notifications. This endpoint is used by external services to publish notifications to users.
        Notifications are validated, stored in the database, and if the user has email notifications enabled, an email is sent.
        Available only in the Processor service.
      operationId: publishNotifications
      tags:
        - Publishing
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                type: object
                properties: &ref_18
                  type:
                    type: string
                    description: Notification type (must be a valid NotificationType enum value)
                    example: worlds_access_restored
                  address:
                    type: string
                    pattern: ^0x[a-fA-F0-9]{40}$
                    description: Ethereum address of the recipient user
                  eventKey:
                    type: string
                    description: Unique identifier for this event (used for deduplication)
                    example: '0xf17828d89d6056215d65451fc61a802f5054fa01aa0acc39b644dc191086dd8b'
                  metadata:
                    type: object
                    description: Notification-specific metadata (content varies by notification type)
                    additionalProperties: true
                  timestamp:
                    type: integer
                    description: Unix timestamp in milliseconds when the event occurred (must be after 2023-01-01)
                    minimum: 1672531200000
                    example: 1701379983000
                required: &ref_19
                  - type
                  - address
                  - eventKey
                  - metadata
                  - timestamp
              minItems: 1
      responses:
        '204':
          description: Notifications published successfully
        '400':
          description: Invalid request body or validation error
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
        '401':
          description: Unauthorized - missing or invalid API key
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
  /notifications/read:
    put:
      summary: Mark notifications as read
      description: |
        Marks specified notifications as read for the authenticated user. This operation is irreversible.
        The user's address is extracted from the signed fetch authentication chain.
      operationId: markNotificationsAsRead
      tags:
        - Notifications
      security:
        - SignedFetch: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties: &ref_14
                notificationIds:
                  type: array
                  items:
                    type: string
                    format: uuid
                  minItems: 1
                  description: Array of notification IDs to mark as read
              required: &ref_15
                - notificationIds
            example:
              notificationIds:
                - 3a33a38a-e17a-4153-926b-c18f1e57e3e1
                - f098bed0-bff9-4ebe-a160-5687ffa4fe36
      responses:
        '200':
          description: Notifications marked as read successfully
          content:
            application/json:
              schema:
                type: object
                properties: &ref_16
                  updated:
                    type: integer
                    description: Number of notifications successfully marked as read
                    example: 2
                required: &ref_17
                  - updated
              example:
                updated: 2
        '400':
          description: Bad request - missing or invalid notificationIds array
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
        '401':
          description: Unauthorized - invalid or missing signed fetch headers
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
        '403':
          description: Forbidden - signed fetch metadata validation failed
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
  /notifications/email:
    post:
      summary: Send common email notification
      description: |
        Sends a generic email notification using a common template. Used by internal services to send custom email notifications.
        Available only in the Inbox service.
      operationId: sendCommonEmail
      tags:
        - Notifications
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties: &ref_20
                email:
                  type: string
                  format: email
                  description: Recipient email address
                subject:
                  type: string
                  description: Email subject line
                content:
                  type: string
                  description: Email body content (HTML)
                title:
                  type: string
                  description: Optional title for the email template
                  default: New
                titleHighlight:
                  type: string
                  description: Optional highlighted portion of the title
                  default: Notification
              required: &ref_21
                - email
                - subject
                - content
      responses:
        '204':
          description: Email sent successfully
        '400':
          description: Invalid request - missing required fields
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
        '401':
          description: Unauthorized - missing or invalid API key
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
  /subscription:
    get:
      summary: Get user subscription preferences
      description: |
        Retrieves the subscription preferences for the authenticated user, including confirmed email, unconfirmed email (if any),
        and detailed notification preferences for each notification type. If no subscription exists, returns default preferences
        with all notifications enabled.
      operationId: getSubscription
      tags:
        - Subscriptions
      security:
        - SignedFetch: []
      responses:
        '200':
          description: User subscription preferences retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties: &ref_22
                  email:
                    oneOf:
                      - type: string
                        format: email
                      - type: 'null'
                    description: Confirmed email address (null if not set)
                  unconfirmedEmail:
                    oneOf:
                      - type: string
                        format: email
                      - type: 'null'
                    description: Email pending confirmation (null if no pending email)
                  details:
                    type: object
                    properties: &ref_2
                      ignore_all_email:
                        type: boolean
                        description: If true, user will not receive any email notifications
                      ignore_all_in_app:
                        type: boolean
                        description: If true, user will not receive any in-app notifications
                      message_type:
                        type: object
                        description: Per-notification-type preferences mapping NotificationType to channel preferences
                        additionalProperties:
                          type: object
                          properties: &ref_25
                            email:
                              type: boolean
                              description: Enable email notifications for this type
                            in_app:
                              type: boolean
                              description: Enable in-app notifications for this type
                          required: &ref_26
                            - email
                            - in_app
                    required: &ref_3
                      - ignore_all_email
                      - ignore_all_in_app
                      - message_type
                    example: &ref_4
                      ignore_all_email: false
                      ignore_all_in_app: false
                      message_type:
                        bid_accepted:
                          email: true
                          in_app: true
                        item_sold:
                          email: false
                          in_app: true
                required: &ref_23
                  - details
                example: &ref_24
                  email: user@example.com
                  unconfirmedEmail: null
                  details:
                    ignore_all_email: false
                    ignore_all_in_app: false
                    message_type:
                      bid_accepted:
                        email: true
                        in_app: true
                      bid_received:
                        email: true
                        in_app: true
        '401':
          description: Unauthorized - invalid or missing signed fetch headers
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
        '403':
          description: Forbidden - signed fetch metadata validation failed
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
    put:
      summary: Update subscription preferences
      description: |
        Updates the subscription preferences for the authenticated user. Allows configuring global email/in-app preferences
        and per-notification-type preferences. The request is validated against the SubscriptionDetails schema.
        Sends an analytics event to the data warehouse.
      operationId: updateSubscription
      tags:
        - Subscriptions
      security:
        - SignedFetch: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties: *ref_2
              required: *ref_3
              example: *ref_4
      responses:
        '204':
          description: Subscription preferences updated successfully
        '400':
          description: Invalid request - validation error in subscription details
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
        '401':
          description: Unauthorized - invalid or missing signed fetch headers
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
        '403':
          description: Forbidden - signed fetch metadata validation failed
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
  /unsubscribe/{address}:
    get:
      summary: Unsubscribe from all email notifications
      description: |
        Unsubscribes a user from all email notifications by setting `ignore_all_email` to true.
        The URL must be signed with HMAC-SHA256 using the service's signing key. Returns an HTML confirmation page.
        This endpoint is typically accessed via links in notification emails.
      operationId: unsubscribeAll
      tags:
        - Subscriptions
      security: []
      parameters:
        - name: address
          in: path
          required: true
          schema:
            type: string
            pattern: ^0x[a-fA-F0-9]{40}$
          description: Ethereum address of the user
          example: '0xb5D7D1A05f553b5098D9274Df6B292e4e8222314'
        - name: signature
          in: query
          required: true
          schema:
            type: string
          description: HMAC-SHA256 signature of the URL path and query parameters
        - name: timestamp
          in: query
          required: true
          schema:
            type: string
          description: Unix timestamp when the signature was generated
      responses:
        '200':
          description: Successfully unsubscribed from all email notifications - returns HTML page
          content:
            text/html:
              schema:
                type: string
                description: HTML confirmation page
        '400':
          description: Invalid signature or expired link
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
  /unsubscribe/{address}/{notificationType}:
    get:
      summary: Unsubscribe from specific notification type
      description: |
        Unsubscribes a user from email notifications for a specific notification type.
        The URL must be signed with HMAC-SHA256 using the service's signing key. Returns an HTML confirmation page.
        This endpoint is typically accessed via links in notification emails.
      operationId: unsubscribeOne
      tags:
        - Subscriptions
      security: []
      parameters:
        - name: address
          in: path
          required: true
          schema:
            type: string
            pattern: ^0x[a-fA-F0-9]{40}$
          description: Ethereum address of the user
          example: '0xb5D7D1A05f553b5098D9274Df6B292e4e8222314'
        - name: notificationType
          in: path
          required: true
          schema:
            type: string
          description: Notification type to unsubscribe from (must be a valid NotificationType enum value)
          example: bid_accepted
        - name: signature
          in: query
          required: true
          schema:
            type: string
          description: HMAC-SHA256 signature of the URL path and query parameters
        - name: timestamp
          in: query
          required: true
          schema:
            type: string
          description: Unix timestamp when the signature was generated
      responses:
        '200':
          description: Successfully unsubscribed from notification type - returns HTML page
          content:
            text/html:
              schema:
                type: string
                description: HTML confirmation page
        '400':
          description: Invalid signature, expired link, or invalid notification type
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
  /set-email:
    put:
      summary: Set or update email address
      description: |
        Initiates email verification process by storing an unconfirmed email and sending a confirmation email.
        - If email is an empty string, removes the current email and disables email notifications
        - If email already exists for another address, returns an error
        - If email matches the currently confirmed email, skips verification
        - Otherwise, generates a 32-character code and sends verification email via SendGrid

        The confirmation email contains a link that may include Turnstile challenge if the feature flag is enabled.
      operationId: storeUnconfirmedEmail
      tags:
        - Subscriptions
      security:
        - SignedFetch: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties: &ref_27
                email:
                  type: string
                  format: email
                  description: Email address to set (provide empty string to remove email and disable notifications)
                redirect:
                  type: string
                  description: Optional redirect URL for email confirmation flow
                isCreditsWorkflow:
                  type: boolean
                  description: Whether this is part of the credits workflow (affects confirmation URL and email template)
                  default: false
              required: &ref_28
                - email
              example: &ref_29
                email: user@example.com
                isCreditsWorkflow: false
      responses:
        '204':
          description: Email verification process initiated successfully (or email removed if empty string provided)
        '400':
          description: Invalid email format or email already registered to another account or domain blacklisted
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
        '401':
          description: Unauthorized - invalid or missing signed fetch headers
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
        '403':
          description: Forbidden - signed fetch metadata validation failed
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
  /confirm-email:
    put:
      summary: Confirm email address
      description: |
        Confirms an email address using the verification code sent via email. Validates the code against the stored
        unconfirmed email entry. If Turnstile verification is enabled, also validates the captcha token.
        Upon success, moves the email to the confirmed subscriptions table and deletes the unconfirmed entry.
      operationId: confirmEmail
      tags:
        - Subscriptions
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties: &ref_30
                address:
                  type: string
                  pattern: ^0x[a-fA-F0-9]{40}$
                  description: Ethereum address of the user confirming the email
                code:
                  type: string
                  minLength: 32
                  maxLength: 32
                  description: 32-character verification code sent via email
                turnstileToken:
                  type: string
                  description: Cloudflare Turnstile captcha token (required when Turnstile verification is enabled)
                source:
                  type: string
                  enum:
                    - credits
                    - account
                  description: Source of the email confirmation (used for analytics)
              required: &ref_31
                - address
                - code
              example: &ref_32
                address: '0xb5D7D1A05f553b5098D9274Df6B292e4e8222314'
                code: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
                source: account
      responses:
        '204':
          description: Email confirmed successfully
        '400':
          description: Invalid or missing confirmation code, address, or email domain blacklisted
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
        '401':
          description: Invalid captcha token (when Turnstile verification is enabled)
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
  /subscription/opt-outs/{scope}/{scopeId}:
    get:
      summary: Check notification opt-out status
      description: |
        Returns whether the authenticated user has opted out from the provided scope and scope identifier.
        The user address is extracted from the signed fetch authentication chain.
      operationId: getNotificationOptOutStatus
      tags:
        - Subscriptions
      security:
        - SignedFetch: []
      parameters:
        - name: scope
          in: path
          required: true
          schema:
            type: string
            enum:
              - community
          description: Scope type for the opt-out
        - name: scopeId
          in: path
          required: true
          schema:
            type: string
            minLength: 1
          description: Identifier scoped to the scope (e.g., community ID)
      responses:
        '200':
          description: Notification opt-out status retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties: &ref_5
                  scope:
                    type: string
                    enum:
                      - community
                    description: Scope type targeted by the opt-out
                  scopeId:
                    type: string
                    description: Identifier scoped to the scope (e.g., community ID)
                  optedOut:
                    type: boolean
                    description: Whether the authenticated user has opted out
                required: &ref_6
                  - scope
                  - scopeId
                  - optedOut
        '401':
          description: Unauthorized - invalid or missing signed fetch headers
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
        '403':
          description: Forbidden - signed fetch metadata validation failed
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
    delete:
      summary: Delete notification opt-out
      description: |
        Removes the opt-out configuration for the specified scope and scope identifier.
      operationId: deleteNotificationOptOut
      tags:
        - Subscriptions
      security:
        - SignedFetch: []
      parameters:
        - name: scope
          in: path
          required: true
          schema:
            type: string
            enum:
              - community
          description: Scope type for the opt-out
        - name: scopeId
          in: path
          required: true
          schema:
            type: string
            minLength: 1
          description: Identifier scoped to the scope (e.g., community ID)
      responses:
        '204':
          description: Notification opt-out deleted successfully
        '401':
          description: Unauthorized - invalid or missing signed fetch headers
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
        '403':
          description: Forbidden - signed fetch metadata validation failed
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
  /subscription/opt-outs:
    post:
      summary: Create notification opt-out
      description: |
        Creates an opt-out configuration for the authenticated user based on a scope and scope identifier pair.
        Each scope maps to a canonical metadata configuration that determines which notifications are filtered.
      operationId: createNotificationOptOut
      tags:
        - Subscriptions
      security:
        - SignedFetch: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties: &ref_33
                scope:
                  type: string
                  enum:
                    - community
                  description: Scope type to opt out from
                scopeId:
                  type: string
                  minLength: 1
                  description: Identifier scoped to the scope (e.g., community ID)
              required: &ref_34
                - scope
                - scopeId
      responses:
        '201':
          description: Notification opt-out created successfully
          content:
            application/json:
              schema:
                type: object
                properties: *ref_5
                required: *ref_6
        '400':
          description: Invalid request - validation error
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
        '401':
          description: Unauthorized - invalid or missing signed fetch headers
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
        '403':
          description: Forbidden - signed fetch metadata validation failed
          content:
            application/json:
              schema:
                type: object
                properties: *ref_0
                required: *ref_1
components:
  securitySchemes:
    SignedFetch:
      type: http
      scheme: bearer
      description: |
        Signed fetch authentication using @dcl/platform-crypto-middleware implementing Decentraland's ADR-44.

        **Requirements:**
        - Authentication chain headers: `x-identity`, `x-signature`, `x-timestamp`
        - Token expiration: 5 minutes
        - Metadata validation: Rejects requests with `signer: "decentraland-kernel-scene"`
        - The user's Ethereum address is extracted from the auth chain

        **Example:**
        ```
        Authorization: Bearer <signed-fetch-token>
        x-identity: <auth-chain-json>
        x-signature: <signature>
        x-timestamp: <timestamp>
        ```
    BearerAuth:
      type: http
      scheme: bearer
      description: |
        Bearer token authentication using a shared API key (`NOTIFICATION_SERVICE_TOKEN`).
        Used for service-to-service communication and external notification publishing.

        **Example:**
        ```
        Authorization: Bearer <api-key>
        ```
  schemas:
    Status:
      type: object
      properties: *ref_7
      required: *ref_8
    NotificationsResponse:
      type: object
      properties: *ref_9
      required: *ref_10
      example: *ref_11
    Notification:
      type: object
      properties: *ref_12
      required: *ref_13
    MarkAsReadRequest:
      type: object
      properties: *ref_14
      required: *ref_15
    MarkAsReadResponse:
      type: object
      properties: *ref_16
      required: *ref_17
    PublishNotificationRequest:
      type: object
      properties: *ref_18
      required: *ref_19
    CommonEmailRequest:
      type: object
      properties: *ref_20
      required: *ref_21
    SubscriptionResponse:
      type: object
      properties: *ref_22
      required: *ref_23
      example: *ref_24
    SubscriptionDetails:
      type: object
      properties: *ref_2
      required: *ref_3
      example: *ref_4
    NotificationChannelType:
      type: object
      properties: *ref_25
      required: *ref_26
    SetEmailRequest:
      type: object
      properties: *ref_27
      required: *ref_28
      example: *ref_29
    ConfirmEmailRequest:
      type: object
      properties: *ref_30
      required: *ref_31
      example: *ref_32
    NotificationOptOut:
      type: object
      properties: *ref_5
      required: *ref_6
    CreateNotificationOptOutRequest:
      type: object
      properties: *ref_33
      required: *ref_34
    Error:
      type: object
      properties: *ref_0
      required: *ref_1
