{
  "openapi": "3.1.0",
  "info": {
    "title": "ReachBell API",
    "description": "Server-to-server API for ReachBell — send transactional messages across push, email, WhatsApp, and SMS, manage subscribers, and ingest customer events. Every request is authenticated with your per-project API key passed in the `x-api-key` header.",
    "version": "1.0.0"
  },
  "servers": [
    { "url": "https://api.reachbell.com" }
  ],
  "security": [
    { "apiKey": [] }
  ],
  "tags": [
    {
      "name": "Transactional",
      "description": "Per-user, real-time sends triggered by your backend (payment succeeded, booking confirmed, OTP, etc.). One message to one recipient, dispatched inline."
    },
    {
      "name": "Subscribers",
      "description": "Register subscriptions and manage subscriber identity and tags. Powers the client SDK's subscribe / identify / tag calls."
    },
    {
      "name": "Events",
      "description": "Ingest customer-defined behavioural events (single or batched) used to trigger automations and build subscriber timelines."
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Your per-project API key. Find it in the project settings of the ReachBell dashboard."
      }
    },
    "schemas": {
      "TransactionalSendRequest": {
        "type": "object",
        "description": "At least one of `externalId` or `token` is required to identify the subscriber. Either supply `title` + `body` directly, or reference a saved `templateId` (with optional `variables`); explicit fields override rendered template values. All URL fields must be HTTPS.",
        "properties": {
          "externalId": {
            "type": "string",
            "maxLength": 256,
            "description": "Your own user ID for the subscriber (set via the SDK's identify() or POST /subscribers/identify). Preferred way to target a user. Either this or `token` must be present.",
            "example": "user_8675309"
          },
          "token": {
            "type": "string",
            "maxLength": 2048,
            "description": "The raw channel-specific subscription token returned at subscribe time. Use this instead of `externalId` if you hold the token server-side. Either this or `externalId` must be present.",
            "example": "{\"endpoint\":\"https://fcm.googleapis.com/fcm/send/abc123\",\"keys\":{\"p256dh\":\"BN...\",\"auth\":\"k9...\"}}"
          },
          "templateId": {
            "type": "string",
            "maxLength": 128,
            "description": "ID of a saved push template to use as the content source. When set, `title`/`body` become optional field-level overrides.",
            "example": "tmpl_order_shipped"
          },
          "variables": {
            "type": "object",
            "additionalProperties": { "type": "string" },
            "description": "Values interpolated into {{placeholders}} in the referenced template.",
            "example": { "orderId": "RB-48213", "name": "Aarav" }
          },
          "title": {
            "type": "string",
            "maxLength": 256,
            "description": "Notification title. Required when no `templateId` is supplied; otherwise overrides the template's title.",
            "example": "Order shipped"
          },
          "body": {
            "type": "string",
            "maxLength": 1024,
            "description": "Notification body. Required when no `templateId` is supplied; otherwise overrides the template's body.",
            "example": "Your order RB-48213 is on the way and arrives tomorrow."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "HTTPS-only click-through URL opened when the notification is tapped.",
            "example": "https://shop.example.in/orders/RB-48213"
          },
          "iconUrl": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "HTTPS-only URL of the notification icon.",
            "example": "https://cdn.example.in/icons/box.png"
          },
          "imageUrl": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "HTTPS-only URL of a large hero image to render in the notification.",
            "example": "https://cdn.example.in/banners/shipped.png"
          },
          "badge": {
            "type": "string",
            "maxLength": 64,
            "description": "Monochrome badge identifier/URL shown on supported platforms.",
            "example": "shipment"
          }
        },
        "example": {
          "externalId": "user_8675309",
          "title": "Order shipped",
          "body": "Your order RB-48213 is on the way and arrives tomorrow.",
          "url": "https://shop.example.in/orders/RB-48213"
        }
      },
      "TransactionalSendResponse": {
        "type": "object",
        "description": "Always returned with HTTP 200 — branch on the fields, not the status code. Failure modes (subscriber not found, frequency-capped, quiet hours, expired subscription, delivery error) are normal and reported via `success`/`delivered`/`reason`.",
        "required": ["success", "delivered", "subscriberFound"],
        "properties": {
          "success": {
            "type": "boolean",
            "description": "True only when the message was accepted and dispatched.",
            "example": true
          },
          "delivered": {
            "type": "boolean",
            "description": "True when the push provider accepted the message for delivery.",
            "example": true
          },
          "subscriberFound": {
            "type": "boolean",
            "description": "Whether a matching active subscriber was resolved.",
            "example": true
          },
          "subscriberId": {
            "type": "string",
            "description": "The resolved subscriber's ID, when one was found.",
            "example": "665f1c9a2d4b8e0012ab34cd"
          },
          "reason": {
            "type": "string",
            "description": "Machine-readable failure reason when `success` is false. One of: title_and_body_required, externalId_or_token_required, template_not_found, subscriber_not_found, channel_not_supported_<type>, project_misconfigured_vapid_keys_missing, project_misconfigured_fcm_credentials_missing, project_misconfigured_apns_credentials_missing, frequency_capped, quiet_hours, subscription_expired_deactivated, delivery_failed_<code>.",
            "example": "subscriber_not_found"
          },
          "channel": {
            "type": "string",
            "description": "Channel used when one was selected.",
            "enum": ["vapid", "fcm", "apns"],
            "example": "fcm"
          }
        },
        "example": {
          "success": true,
          "delivered": true,
          "subscriberFound": true,
          "subscriberId": "665f1c9a2d4b8e0012ab34cd",
          "channel": "fcm"
        }
      },
      "TransactionalEmailRequest": {
        "type": "object",
        "description": "`senderEmail` and `replyTo`, when supplied, must be on the project's verified sending domain or the request is rejected with 400. `senderEmail`/`senderName` fall back to the project's configured defaults when omitted.",
        "required": ["to", "subject", "htmlContent"],
        "properties": {
          "to": {
            "type": "string",
            "format": "email",
            "maxLength": 320,
            "description": "Recipient email address.",
            "example": "aarav.sharma@gmail.com"
          },
          "subject": {
            "type": "string",
            "maxLength": 998,
            "description": "Email subject line.",
            "example": "Your ReachBell receipt for order RB-48213"
          },
          "htmlContent": {
            "type": "string",
            "maxLength": 1000000,
            "description": "HTML body of the email (up to 1 MB, matching the provider's per-message limit).",
            "example": "<h1>Thanks for your order</h1><p>Order RB-48213 ships today.</p>"
          },
          "textContent": {
            "type": "string",
            "maxLength": 1000000,
            "description": "Optional plain-text alternative body.",
            "example": "Thanks for your order. Order RB-48213 ships today."
          },
          "senderEmail": {
            "type": "string",
            "format": "email",
            "maxLength": 320,
            "description": "From address. Must be on the project's verified sending domain. Defaults to the project's configured sender.",
            "example": "receipts@example.in"
          },
          "senderName": {
            "type": "string",
            "maxLength": 120,
            "description": "Display name for the From address. Defaults to the project name.",
            "example": "Example Shop"
          },
          "replyTo": {
            "type": "string",
            "format": "email",
            "maxLength": 320,
            "description": "Reply-To address. Must be on the project's verified sending domain.",
            "example": "support@example.in"
          }
        },
        "example": {
          "to": "aarav.sharma@gmail.com",
          "subject": "Your ReachBell receipt for order RB-48213",
          "htmlContent": "<h1>Thanks for your order</h1><p>Order RB-48213 ships today.</p>",
          "senderEmail": "receipts@example.in",
          "senderName": "Example Shop"
        }
      },
      "TransactionalEmailResponse": {
        "type": "object",
        "required": ["success"],
        "properties": {
          "success": {
            "type": "boolean",
            "description": "Whether the email was accepted by the sending provider.",
            "example": true
          },
          "messageId": {
            "type": "string",
            "description": "Provider message ID for the accepted email.",
            "example": "0100018f-2a1b-4c3d-9e8f-aabbccddeeff-000000"
          },
          "error": {
            "type": "string",
            "description": "Error string when `success` is false.",
            "example": "MessageRejected: Email address is not verified"
          }
        },
        "example": {
          "success": true,
          "messageId": "0100018f-2a1b-4c3d-9e8f-aabbccddeeff-000000"
        }
      },
      "TransactionalWhatsappRequest": {
        "type": "object",
        "description": "Identify the recipient by `to` (E.164 phone, wins when present) or `externalId` (resolves to the identified subscriber's phone, applying consent gates). Supply either an approved `template` or free-text `text`.",
        "properties": {
          "to": {
            "type": "string",
            "description": "Destination phone in E.164 format. Takes precedence over `externalId`; consent for direct sends is the caller's responsibility.",
            "example": "+919812345678"
          },
          "externalId": {
            "type": "string",
            "description": "Your user ID; resolves to the subscriber's phone on file and enforces opt-out / pause / per-channel preferences. Required if `to` is omitted.",
            "example": "user_8675309"
          },
          "template": {
            "type": "object",
            "description": "An approved WhatsApp template to send. Required outside an open 24-hour customer-service window.",
            "required": ["name"],
            "properties": {
              "name": {
                "type": "string",
                "description": "Approved template name registered with your WhatsApp provider.",
                "example": "order_shipped_v2"
              },
              "params": {
                "type": "array",
                "items": { "type": "string" },
                "description": "Ordered substitution values for the template's placeholders.",
                "example": ["Aarav", "RB-48213"]
              }
            }
          },
          "text": {
            "type": "string",
            "description": "Free-text message body. Only delivered inside an open 24-hour session; otherwise use `template`.",
            "example": "Your order RB-48213 has shipped and arrives tomorrow."
          }
        },
        "example": {
          "externalId": "user_8675309",
          "template": { "name": "order_shipped_v2", "params": ["Aarav", "RB-48213"] }
        }
      },
      "TransactionalSmsRequest": {
        "type": "object",
        "description": "Identify the recipient by `to` (E.164 phone, wins when present) or `externalId` (resolves to the identified subscriber's phone, applying consent gates). `text` is required.",
        "required": ["text"],
        "properties": {
          "to": {
            "type": "string",
            "description": "Destination phone in E.164 format. Takes precedence over `externalId`.",
            "example": "+919812345678"
          },
          "externalId": {
            "type": "string",
            "description": "Your user ID; resolves to the subscriber's phone on file and enforces consent. Required if `to` is omitted.",
            "example": "user_8675309"
          },
          "text": {
            "type": "string",
            "description": "SMS message body.",
            "example": "Your OTP for Example Shop is 482130. Valid for 10 minutes."
          },
          "dltTemplateId": {
            "type": "string",
            "description": "India DLT template ID for the message content, required by Indian SMS providers for regulatory compliance.",
            "example": "1707161234567890123"
          }
        },
        "example": {
          "to": "+919812345678",
          "text": "Your OTP for Example Shop is 482130. Valid for 10 minutes.",
          "dltTemplateId": "1707161234567890123"
        }
      },
      "PhoneSendResponse": {
        "type": "object",
        "required": ["status", "to"],
        "properties": {
          "status": {
            "type": "string",
            "description": "Send outcome. `sent` for live delivery, `sandboxed` when the project is in sandbox mode (message suppressed), `simulated` when the provider ran in simulation.",
            "enum": ["sent", "sandboxed", "simulated"],
            "example": "sent"
          },
          "to": {
            "type": "string",
            "description": "The resolved destination phone number.",
            "example": "+919812345678"
          },
          "providerMessageId": {
            "type": "string",
            "description": "The downstream provider's message ID, when returned.",
            "example": "wamid.HBgMOTE5ODEyMzQ1Njc4FQIAERgSN0Ex"
          }
        },
        "example": {
          "status": "sent",
          "to": "+919812345678",
          "providerMessageId": "wamid.HBgMOTE5ODEyMzQ1Njc4FQIAERgSN0Ex"
        }
      },
      "SubscriberRegisterRequest": {
        "type": "object",
        "description": "Register (or upsert) a subscription. Upserted on (project, token). The server enriches the record with geo and user-agent data, so only the channel token and identity fields below need to be sent. Server-controlled fields (consent/suppression state) are ignored if supplied.",
        "required": ["token", "tokenType"],
        "properties": {
          "token": {
            "type": "string",
            "description": "Channel-specific subscription token: full stringified PushSubscription JSON (vapid), device registration token (fcm), or device token (apns).",
            "example": "{\"endpoint\":\"https://fcm.googleapis.com/fcm/send/abc123\",\"keys\":{\"p256dh\":\"BN...\",\"auth\":\"k9...\"}}"
          },
          "tokenType": {
            "type": "string",
            "description": "Which push channel the token belongs to.",
            "enum": ["vapid", "fcm", "apns"],
            "example": "vapid"
          },
          "platform": {
            "type": "string",
            "description": "Originating platform (e.g. web, android, ios).",
            "example": "web"
          },
          "externalId": {
            "type": "string",
            "description": "Your own user ID to associate with this subscription (can also be set later via /subscribers/identify).",
            "example": "user_8675309"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Subscriber email for the email channel.",
            "example": "aarav.sharma@gmail.com"
          },
          "phone": {
            "type": "string",
            "description": "Subscriber phone in E.164 format for WhatsApp/SMS channels.",
            "example": "+919812345678"
          },
          "tags": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Initial tags to attach to the subscription.",
            "example": ["pro", "mumbai"]
          },
          "language": {
            "type": "string",
            "description": "Subscriber language (BCP-47). Server geo/UA enrichment fills the rest.",
            "example": "en-IN"
          },
          "timezone": {
            "type": "string",
            "description": "Subscriber IANA timezone. Overridden by server geo lookup when resolvable.",
            "example": "Asia/Kolkata"
          }
        },
        "example": {
          "token": "{\"endpoint\":\"https://fcm.googleapis.com/fcm/send/abc123\",\"keys\":{\"p256dh\":\"BN...\",\"auth\":\"k9...\"}}",
          "tokenType": "vapid",
          "platform": "web",
          "externalId": "user_8675309",
          "tags": ["pro", "mumbai"]
        }
      },
      "SubscriberResponse": {
        "type": "object",
        "description": "The created or updated subscriber document.",
        "properties": {
          "_id": { "type": "string", "example": "665f1c9a2d4b8e0012ab34cd" },
          "projectId": { "type": "string", "example": "664a0b112c3d4e5f60718293" },
          "tokenType": { "type": "string", "example": "vapid" },
          "platform": { "type": "string", "example": "web" },
          "externalId": { "type": "string", "example": "user_8675309" },
          "country": { "type": "string", "example": "IN" },
          "countryName": { "type": "string", "example": "India" },
          "city": { "type": "string", "example": "Mumbai" },
          "timezone": { "type": "string", "example": "Asia/Kolkata" },
          "tags": { "type": "array", "items": { "type": "string" }, "example": ["pro", "mumbai"] },
          "isActive": { "type": "boolean", "example": true },
          "subscribedAt": { "type": "string", "format": "date-time", "example": "2026-06-26T09:14:02.511Z" }
        },
        "example": {
          "_id": "665f1c9a2d4b8e0012ab34cd",
          "projectId": "664a0b112c3d4e5f60718293",
          "tokenType": "vapid",
          "platform": "web",
          "externalId": "user_8675309",
          "country": "IN",
          "countryName": "India",
          "city": "Mumbai",
          "timezone": "Asia/Kolkata",
          "tags": ["pro", "mumbai"],
          "isActive": true,
          "subscribedAt": "2026-06-26T09:14:02.511Z"
        }
      },
      "IdentifyRequest": {
        "type": "object",
        "description": "Bind your user ID to an existing subscription, matched by (project, token). Idempotent — re-identifying with a different `externalId` rebinds the subscription.",
        "required": ["token", "externalId"],
        "properties": {
          "token": {
            "type": "string",
            "description": "The raw subscription token of an existing subscriber.",
            "example": "{\"endpoint\":\"https://fcm.googleapis.com/fcm/send/abc123\",\"keys\":{\"p256dh\":\"BN...\",\"auth\":\"k9...\"}}"
          },
          "externalId": {
            "type": "string",
            "description": "Your user ID to associate with this subscription.",
            "example": "user_8675309"
          }
        },
        "example": {
          "token": "{\"endpoint\":\"https://fcm.googleapis.com/fcm/send/abc123\",\"keys\":{\"p256dh\":\"BN...\",\"auth\":\"k9...\"}}",
          "externalId": "user_8675309"
        }
      },
      "TagRequest": {
        "type": "object",
        "description": "Add or remove tags on an existing subscription, matched by (project, token). Tags are normalised: trimmed, de-duplicated, capped at 50 per subscriber and 100 characters each. Adding is additive (existing tags are preserved).",
        "required": ["token", "tags"],
        "properties": {
          "token": {
            "type": "string",
            "description": "The raw subscription token of an existing subscriber.",
            "example": "{\"endpoint\":\"https://fcm.googleapis.com/fcm/send/abc123\",\"keys\":{\"p256dh\":\"BN...\",\"auth\":\"k9...\"}}"
          },
          "tags": {
            "type": "array",
            "items": { "type": "string", "maxLength": 100 },
            "minItems": 1,
            "maxItems": 50,
            "description": "Tags to add or remove. Up to 50 tags, each up to 100 characters.",
            "example": ["pro", "mumbai", "high-value"]
          }
        },
        "example": {
          "token": "{\"endpoint\":\"https://fcm.googleapis.com/fcm/send/abc123\",\"keys\":{\"p256dh\":\"BN...\",\"auth\":\"k9...\"}}",
          "tags": ["pro", "high-value"]
        }
      },
      "MutationResult": {
        "type": "object",
        "description": "Result of an identify / tag / untag mutation.",
        "required": ["success"],
        "properties": {
          "success": {
            "type": "boolean",
            "description": "Whether the matching subscription was found and the operation accepted.",
            "example": true
          },
          "updated": {
            "type": "boolean",
            "description": "Whether the document actually changed (false on a no-op).",
            "example": true
          },
          "reason": {
            "type": "string",
            "description": "Failure reason when `success` is false: token_and_externalId_required, token_and_tags_required, subscriber_not_found, or no_valid_tags.",
            "example": "subscriber_not_found"
          }
        },
        "example": { "success": true, "updated": true }
      },
      "EventIngestSingle": {
        "type": "object",
        "description": "A single customer event. Resolve the subscriber by `subscriberId` (wins) or `externalId`; an unresolved event is still stored.",
        "required": ["name"],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 64,
            "description": "Event name. Normalised server-side to lowercase with non [a-z0-9_.-] characters replaced by underscores, max 64 chars.",
            "example": "purchase_completed"
          },
          "externalId": {
            "type": "string",
            "description": "Your user ID, used to resolve the subscriber when `subscriberId` is absent.",
            "example": "user_8675309"
          },
          "subscriberId": {
            "type": "string",
            "description": "ReachBell subscriber ID. Takes precedence over `externalId` for resolution.",
            "example": "665f1c9a2d4b8e0012ab34cd"
          },
          "properties": {
            "type": "object",
            "additionalProperties": true,
            "description": "Arbitrary JSON properties for the event. The serialised payload must be at most 4 KB.",
            "example": { "orderId": "RB-48213", "amount": 1499, "currency": "INR" }
          },
          "source": {
            "type": "string",
            "description": "Origin hint. Defaults to `api`; pass `sdk` when forwarding from a client SDK.",
            "enum": ["api", "sdk"],
            "example": "api"
          }
        },
        "example": {
          "name": "purchase_completed",
          "externalId": "user_8675309",
          "properties": { "orderId": "RB-48213", "amount": 1499, "currency": "INR" }
        }
      },
      "EventIngestBatch": {
        "type": "object",
        "description": "A batch of up to 25 events. Each entry has the same shape as a single event (without `source`, which is set once at the top level). Events beyond the 25th are ignored.",
        "required": ["events"],
        "properties": {
          "events": {
            "type": "array",
            "minItems": 1,
            "maxItems": 25,
            "description": "The events to ingest (maximum 25 per call).",
            "items": {
              "type": "object",
              "required": ["name"],
              "properties": {
                "name": {
                  "type": "string",
                  "maxLength": 64,
                  "description": "Event name (normalised server-side, max 64 chars).",
                  "example": "add_to_cart"
                },
                "externalId": {
                  "type": "string",
                  "description": "Your user ID used to resolve the subscriber.",
                  "example": "user_8675309"
                },
                "subscriberId": {
                  "type": "string",
                  "description": "ReachBell subscriber ID (wins over externalId).",
                  "example": "665f1c9a2d4b8e0012ab34cd"
                },
                "properties": {
                  "type": "object",
                  "additionalProperties": true,
                  "description": "Arbitrary JSON properties (serialised payload at most 4 KB).",
                  "example": { "sku": "TSHIRT-BLK-M", "qty": 2 }
                }
              }
            }
          },
          "source": {
            "type": "string",
            "description": "Origin hint applied to every event in the batch. Defaults to `api`.",
            "enum": ["api", "sdk"],
            "example": "api"
          }
        },
        "example": {
          "events": [
            { "name": "add_to_cart", "externalId": "user_8675309", "properties": { "sku": "TSHIRT-BLK-M", "qty": 2 } },
            { "name": "checkout_started", "externalId": "user_8675309", "properties": { "cartValue": 2998 } }
          ]
        }
      },
      "EventIngestResponse": {
        "type": "object",
        "description": "Response for a single event ingest.",
        "required": ["id", "name", "resolved"],
        "properties": {
          "id": {
            "type": "string",
            "description": "ID of the stored event.",
            "example": "665f2a3b4c5d6e7f80910111"
          },
          "name": {
            "type": "string",
            "description": "The normalised event name.",
            "example": "purchase_completed"
          },
          "resolved": {
            "type": "boolean",
            "description": "Whether the event was linked to a known subscriber.",
            "example": true
          }
        },
        "example": {
          "id": "665f2a3b4c5d6e7f80910111",
          "name": "purchase_completed",
          "resolved": true
        }
      },
      "EventBatchResponse": {
        "type": "object",
        "description": "Response for a batch ingest. `results` is positionally aligned with the submitted `events`; a failed entry carries an `error` instead of the success fields.",
        "required": ["accepted", "results"],
        "properties": {
          "accepted": {
            "type": "integer",
            "description": "Number of events processed (capped at 25).",
            "example": 2
          },
          "results": {
            "type": "array",
            "description": "Per-event result, in submission order.",
            "items": {
              "type": "object",
              "properties": {
                "id": { "type": "string", "example": "665f2a3b4c5d6e7f80910111" },
                "name": { "type": "string", "example": "add_to_cart" },
                "resolved": { "type": "boolean", "example": true },
                "error": { "type": "string", "example": "event name is required" }
              }
            }
          }
        },
        "example": {
          "accepted": 2,
          "results": [
            { "id": "665f2a3b4c5d6e7f80910111", "name": "add_to_cart", "resolved": true },
            { "id": "665f2a3b4c5d6e7f80910112", "name": "checkout_started", "resolved": true }
          ]
        }
      }
    }
  },
  "paths": {
    "/transactional/send": {
      "post": {
        "tags": ["Transactional"],
        "summary": "Send a transactional push",
        "description": "Deliver a single push notification to one identified subscriber, dispatched inline (web push / FCM / APNs, routed by the subscriber's token type). Identify the recipient by `externalId` or `token`. Provide `title` + `body` or reference a `templateId`. URL fields (`url`, `iconUrl`, `imageUrl`) must be HTTPS. This endpoint ALWAYS returns HTTP 200 with a `{ success, delivered, subscriberFound, reason, ... }` envelope — branch on those fields, not the status code; project-wide frequency caps and quiet hours apply. Rate limit: 120 requests/min/IP.",
        "operationId": "sendTransactionalPush",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/TransactionalSendRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Always-200 send envelope. `success`/`delivered` indicate the outcome; `reason` explains any failure.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TransactionalSendResponse" },
                "example": {
                  "success": true,
                  "delivered": true,
                  "subscriberFound": true,
                  "subscriberId": "665f1c9a2d4b8e0012ab34cd",
                  "channel": "fcm"
                }
              }
            }
          }
        }
      }
    },
    "/transactional/email": {
      "post": {
        "tags": ["Transactional"],
        "summary": "Send a transactional email",
        "description": "Send a single transactional email. `senderEmail` and `replyTo`, when supplied, must be on the project's verified sending domain (otherwise 400); they fall back to the project defaults when omitted. `htmlContent` is capped at 1 MB. Rate limit: 120 requests/min/IP.",
        "operationId": "sendTransactionalEmail",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/TransactionalEmailRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Email accepted (or rejected) by the sending provider.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TransactionalEmailResponse" },
                "example": {
                  "success": true,
                  "messageId": "0100018f-2a1b-4c3d-9e8f-aabbccddeeff-000000"
                }
              }
            }
          }
        }
      }
    },
    "/transactional/whatsapp": {
      "post": {
        "tags": ["Transactional"],
        "summary": "Send a transactional WhatsApp message",
        "description": "Send a single WhatsApp message. Target by `to` (E.164, wins) or `externalId` (resolves the subscriber's phone and enforces opt-out / pause / per-channel consent). Provide an approved `template` or free-text `text`; free text is only delivered inside an open 24-hour customer-service window, otherwise use a template. Requires the WhatsApp channel to be enabled and configured and included in your plan. Rate limit: 120 requests/min/IP.",
        "operationId": "sendTransactionalWhatsapp",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/TransactionalWhatsappRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "WhatsApp message accepted for delivery.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PhoneSendResponse" },
                "example": {
                  "status": "sent",
                  "to": "+919812345678",
                  "providerMessageId": "wamid.HBgMOTE5ODEyMzQ1Njc4FQIAERgSN0Ex"
                }
              }
            }
          }
        }
      }
    },
    "/transactional/sms": {
      "post": {
        "tags": ["Transactional"],
        "summary": "Send a transactional SMS",
        "description": "Send a single SMS. Target by `to` (E.164, wins) or `externalId` (resolves the subscriber's phone and enforces consent). `text` is required; pass `dltTemplateId` where Indian DLT compliance requires it. Requires the SMS channel to be enabled and configured and included in your plan. Rate limit: 120 requests/min/IP.",
        "operationId": "sendTransactionalSms",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/TransactionalSmsRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SMS accepted for delivery.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PhoneSendResponse" },
                "example": {
                  "status": "sent",
                  "to": "+919812345678",
                  "providerMessageId": "SMb1c2d3e4f5a6978899aabbccddeeff00"
                }
              }
            }
          }
        }
      }
    },
    "/subscribers": {
      "post": {
        "tags": ["Subscribers"],
        "summary": "Register a subscriber",
        "description": "Register (upsert) a subscription for the project. Upserted on (project, token); the server enriches the record with geo and user-agent data. Subject to your plan's push-subscriber limit (403 when exceeded). Server-controlled consent/suppression fields are ignored if supplied. Rate limit: 120 requests/min/IP.",
        "operationId": "registerSubscriber",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SubscriberRegisterRequest" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created or updated subscriber.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SubscriberResponse" },
                "example": {
                  "_id": "665f1c9a2d4b8e0012ab34cd",
                  "projectId": "664a0b112c3d4e5f60718293",
                  "tokenType": "vapid",
                  "platform": "web",
                  "externalId": "user_8675309",
                  "country": "IN",
                  "countryName": "India",
                  "city": "Mumbai",
                  "timezone": "Asia/Kolkata",
                  "tags": ["pro", "mumbai"],
                  "isActive": true,
                  "subscribedAt": "2026-06-26T09:14:02.511Z"
                }
              }
            }
          }
        }
      }
    },
    "/subscribers/identify": {
      "post": {
        "tags": ["Subscribers"],
        "summary": "Identify a subscriber",
        "description": "Attach your user ID (`externalId`) to an existing subscription, matched by (project, token). Idempotent — re-identifying with a different `externalId` rebinds the subscription. The subscription must already exist (created via POST /subscribers). Rate limit: 120 requests/min/IP.",
        "operationId": "identifySubscriber",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/IdentifyRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Identify result.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MutationResult" },
                "example": { "success": true, "updated": true }
              }
            }
          }
        }
      }
    },
    "/subscribers/tag": {
      "post": {
        "tags": ["Subscribers"],
        "summary": "Add tags to a subscriber",
        "description": "Additively add tags to an existing subscription, matched by (project, token). Tags are trimmed, de-duplicated, and capped at 50 per subscriber and 100 characters each; repeated calls never duplicate. Rate limit: 120 requests/min/IP.",
        "operationId": "tagSubscriber",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/TagRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tag result.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MutationResult" },
                "example": { "success": true, "updated": true }
              }
            }
          }
        }
      }
    },
    "/subscribers/untag": {
      "post": {
        "tags": ["Subscribers"],
        "summary": "Remove tags from a subscriber",
        "description": "Remove tags from an existing subscription, matched by (project, token). Tags are normalised the same way as for tagging (≤50 tags, ≤100 chars each). Rate limit: 120 requests/min/IP.",
        "operationId": "untagSubscriber",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/TagRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Untag result.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MutationResult" },
                "example": { "success": true, "updated": true }
              }
            }
          }
        }
      }
    },
    "/events": {
      "post": {
        "tags": ["Events"],
        "summary": "Ingest event(s)",
        "description": "Ingest a customer-defined event, or a batch of them. Send a single event object, OR an object with an `events` array (batch — capped at 25 per call; entries beyond the 25th are ignored). Each event resolves a subscriber by `subscriberId` (wins) or `externalId`; unresolved events are still stored. Event `name` is normalised to lowercase (max 64 chars) and the serialised `properties` payload must be at most 4 KB. Resolved events enroll the subscriber into matching automations. Rate limit: 120 requests/min/IP.",
        "operationId": "ingestEvents",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  { "$ref": "#/components/schemas/EventIngestSingle" },
                  { "$ref": "#/components/schemas/EventIngestBatch" }
                ]
              },
              "examples": {
                "single": {
                  "summary": "Single event",
                  "value": {
                    "name": "purchase_completed",
                    "externalId": "user_8675309",
                    "properties": { "orderId": "RB-48213", "amount": 1499, "currency": "INR" }
                  }
                },
                "batch": {
                  "summary": "Batch of events (max 25)",
                  "value": {
                    "events": [
                      { "name": "add_to_cart", "externalId": "user_8675309", "properties": { "sku": "TSHIRT-BLK-M", "qty": 2 } },
                      { "name": "checkout_started", "externalId": "user_8675309", "properties": { "cartValue": 2998 } }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Single-event response (`{ id, name, resolved }`) or batch response (`{ accepted, results }`), matching the request shape.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    { "$ref": "#/components/schemas/EventIngestResponse" },
                    { "$ref": "#/components/schemas/EventBatchResponse" }
                  ]
                },
                "examples": {
                  "single": {
                    "summary": "Single-event result",
                    "value": { "id": "665f2a3b4c5d6e7f80910111", "name": "purchase_completed", "resolved": true }
                  },
                  "batch": {
                    "summary": "Batch result",
                    "value": {
                      "accepted": 2,
                      "results": [
                        { "id": "665f2a3b4c5d6e7f80910111", "name": "add_to_cart", "resolved": true },
                        { "id": "665f2a3b4c5d6e7f80910112", "name": "checkout_started", "resolved": true }
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
