{"openapi":"3.0.0","paths":{"/v1/email/campaigns":{"post":{"description":"Create a new email campaign in Draft status. Configure recipients via `list_ids`, `tag_ids` and/or `segment_ids`.","operationId":"email.campaigns.create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEmailCampaignDevApiDto"}}}},"responses":{"201":{"description":"Campaign created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCampaignIdResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Create an email campaign","tags":["Email Campaigns"]},"get":{"description":"Retrieve a paginated list of email campaigns. Supports search, status filter, and sorting.","operationId":"email.campaigns.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}},{"name":"search","required":false,"in":"query","description":"Search by campaign name","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["draft","scheduled","queued","sending","sent","paused","failed","cancelled"]}}],"responses":{"200":{"description":"List of campaigns","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCampaignListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List email campaigns","tags":["Email Campaigns"]}},"/v1/email/campaigns/{id}":{"get":{"description":"Retrieve an email campaign by ID.","operationId":"email.campaigns.get","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCampaignResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get an email campaign","tags":["Email Campaigns"]},"patch":{"description":"Update a campaign. Only Draft or Scheduled campaigns can be updated.","operationId":"email.campaigns.update","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEmailCampaignDevApiDto"}}}},"responses":{"200":{"description":"Campaign updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCampaignIdResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Update an email campaign","tags":["Email Campaigns"]},"delete":{"description":"Soft-delete an email campaign.","operationId":"email.campaigns.remove","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Delete an email campaign","tags":["Email Campaigns"]}},"/v1/email/campaigns/{id}/send":{"post":{"description":"Queue a campaign for sending. Pass `scheduled_at` (future ISO 8601) to schedule, or omit it to send immediately. Campaign must be in Draft or Scheduled status with all required fields configured.","operationId":"email.campaigns.send","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"Optional key (max 256 chars) that makes a retry of this request safe for 24h: a repeat returns the first response instead of acting twice.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendEmailCampaignDevApiDto"}}}},"responses":{"200":{"description":"Campaign queued or scheduled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCampaignIdResponseDto"}}}},"400":{"description":"Campaign not ready to send or invalid schedule time","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"Idempotency-Key reused with a different payload or for a different campaign","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"503":{"description":"A request with this Idempotency-Key is still processing — retry shortly","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Send or schedule an email campaign","tags":["Email Campaigns"]}},"/v1/email/campaigns/{id}/cancel":{"post":{"description":"Cancel a Scheduled, Queued, or Sending campaign.","operationId":"email.campaigns.cancel","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"Optional key (max 256 chars) that makes a retry of this request safe for 24h: a repeat returns the first response instead of acting twice.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCampaignIdResponseDto"}}}},"400":{"description":"Campaign cannot be cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"Idempotency-Key reused with a different payload or for a different campaign","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"503":{"description":"A request with this Idempotency-Key is still processing — retry shortly","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Cancel an email campaign","tags":["Email Campaigns"]}},"/v1/email/domains":{"post":{"description":"Register a domain to send from and get the DNS records to publish at its DNS provider. The domain starts `pending` and becomes `verified` once the required records are found; this is checked periodically.\n\nThe call is idempotent: registering a domain the organization already has returns its current state. After publishing the records, call `POST /v1/email/domains/{id}/verify` to check them without waiting.","operationId":"email.domains.create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterEmailDomainDevApiDto"}}}},"responses":{"200":{"description":"The domain and its DNS records","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDomainDetailResponseDto"}}}},"400":{"description":"The domain, or a parent or subdomain of it, is verified by another organization (`domain_unavailable`), or the organization has reached its plan's domain limit (`domain_limit_reached`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"Another domain is being registered for the organization. Register domains one at a time.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Register a sending domain","tags":["Email Domains"]},"get":{"description":"Retrieve a paginated list of the organization's sending domains and their verification status. Register one with `POST /v1/email/domains`.","operationId":"email.domains.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"List of sending domains","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDomainListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List sending domains","tags":["Email Domains"]}},"/v1/email/domains/{id}":{"get":{"description":"Retrieve a sending domain with its DNS records and their status as of the last check.","operationId":"email.domains.get","parameters":[{"name":"id","required":true,"in":"path","description":"Domain ID","schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"The domain and its DNS records","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDomainDetailResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Domain not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get a sending domain","tags":["Email Domains"]},"delete":{"description":"Remove a sending domain. Its addresses can no longer be used as a sender.","operationId":"email.domains.remove","parameters":[{"name":"id","required":true,"in":"path","description":"Domain ID","schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"Domain deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Domain not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Delete a sending domain","tags":["Email Domains"]}},"/v1/email/domains/{id}/verify":{"post":{"description":"Check the domain's DNS records now, without waiting for the periodic check, and return the result. A domain that is already `verified` is returned as it is.","operationId":"email.domains.verify","parameters":[{"name":"id","required":true,"in":"path","description":"Domain ID","schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"The domain and its DNS records after the check","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDomainDetailResponseDto"}}}},"400":{"description":"A parent or subdomain of the domain has since been verified by another organization (`domain_unavailable`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Domain not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Verify a sending domain","tags":["Email Domains"]}},"/v1/sms/campaigns":{"post":{"description":"Create a new SMS campaign in Draft status. Configure recipients via `list_ids`, `tag_ids` and/or `segment_ids`.","operationId":"sms.campaigns.create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSmsCampaignDevApiDto"}}}},"responses":{"201":{"description":"Campaign created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsCampaignIdResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Create an SMS campaign","tags":["SMS Campaigns"]},"get":{"description":"Retrieve a paginated list of SMS campaigns. Supports search, status filter, and sorting.","operationId":"sms.campaigns.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}},{"name":"search","required":false,"in":"query","description":"Search by campaign name","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["draft","scheduled","queued","sending","sent","paused","failed","cancelled"]}}],"responses":{"200":{"description":"List of campaigns","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsCampaignListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List SMS campaigns","tags":["SMS Campaigns"]}},"/v1/sms/campaigns/{id}":{"get":{"description":"Retrieve an SMS campaign by ID.","operationId":"sms.campaigns.get","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsCampaignResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get an SMS campaign","tags":["SMS Campaigns"]},"patch":{"description":"Update a campaign. Only Draft or Scheduled campaigns can be updated.","operationId":"sms.campaigns.update","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSmsCampaignDevApiDto"}}}},"responses":{"200":{"description":"Campaign updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsCampaignIdResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Update an SMS campaign","tags":["SMS Campaigns"]},"delete":{"description":"Soft-delete an SMS campaign.","operationId":"sms.campaigns.remove","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Delete an SMS campaign","tags":["SMS Campaigns"]}},"/v1/sms/campaigns/{id}/send":{"post":{"description":"Queue a campaign for sending. Pass `scheduled_at` (future ISO 8601) to schedule, or omit it to send immediately. Campaign must be in Draft or Scheduled status with all required fields.","operationId":"sms.campaigns.send","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"Optional key (max 256 chars) that makes a retry of this request safe for 24h: a repeat returns the first response instead of acting twice.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendSmsCampaignDevApiDto"}}}},"responses":{"200":{"description":"Campaign queued or scheduled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsCampaignIdResponseDto"}}}},"400":{"description":"Campaign not ready to send or invalid schedule time","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"Idempotency-Key reused with a different payload or for a different campaign","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"503":{"description":"A request with this Idempotency-Key is still processing — retry shortly","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Send or schedule an SMS campaign","tags":["SMS Campaigns"]}},"/v1/sms/campaigns/{id}/cancel":{"post":{"description":"Cancel a Scheduled, Queued, or Sending campaign.","operationId":"sms.campaigns.cancel","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"Optional key (max 256 chars) that makes a retry of this request safe for 24h: a repeat returns the first response instead of acting twice.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsCampaignIdResponseDto"}}}},"400":{"description":"Campaign cannot be cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"Idempotency-Key reused with a different payload or for a different campaign","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"503":{"description":"A request with this Idempotency-Key is still processing — retry shortly","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Cancel an SMS campaign","tags":["SMS Campaigns"]}},"/v1/sms/senders":{"get":{"description":"Retrieve a paginated list of the organization's approved SMS sender names. Senders awaiting approval or rejected are not included.","operationId":"sms.senders.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"List of approved SMS senders","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsSenderListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List SMS senders","tags":["SMS Senders"]}},"/v1/templates/gallery/categories":{"get":{"description":"Return the available filter values (types, seasons, features, industries) you can pass to `GET /templates/gallery`.","operationId":"templates.gallery.listCategories","parameters":[],"responses":{"200":{"description":"Available categories","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GalleryCategoriesResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List gallery categories","tags":["Template Gallery"]}},"/v1/templates/gallery":{"get":{"description":"Return a paginated list of starter email templates. Use `GET /templates/gallery/:id` to fetch a specific template with its full HTML.","operationId":"templates.gallery.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}},{"name":"search","required":false,"in":"query","description":"Search by template name","schema":{"type":"string"}},{"name":"template_types","required":false,"in":"query","description":"Comma-separated list of type IDs (from `GET /templates/gallery/categories`).","schema":{"example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a,01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6b","type":"string"}},{"name":"template_seasons","required":false,"in":"query","description":"Comma-separated list of season IDs.","schema":{"example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6c","type":"string"}},{"name":"template_features","required":false,"in":"query","description":"Comma-separated list of feature IDs.","schema":{"example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6d,01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6e","type":"string"}},{"name":"template_industries","required":false,"in":"query","description":"Comma-separated list of industry IDs.","schema":{"example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6f","type":"string"}}],"responses":{"200":{"description":"Gallery templates","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GalleryListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Browse the gallery","tags":["Template Gallery"]}},"/v1/templates/gallery/{id}":{"get":{"description":"Retrieve a starter template by ID, including the full HTML you can pass to `POST /templates` to save your own customized version.","operationId":"templates.gallery.get","parameters":[{"name":"id","required":true,"in":"path","description":"Gallery template ID (UUID)","schema":{"type":"string"}}],"responses":{"200":{"description":"Gallery template details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GalleryDetailResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get a gallery template","tags":["Template Gallery"]}},"/v1/templates/gallery/{id}/copy":{"post":{"description":"Creates a new custom template owned by your organization, populated with the gallery template's HTML. The returned `id` can be used as `template_id` when creating an email campaign.","operationId":"templates.gallery.copy","parameters":[{"name":"id","required":true,"in":"path","description":"Gallery template ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CopyGalleryTemplateDevApiDto"}}}},"responses":{"201":{"description":"Custom template created from gallery","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CopyGalleryTemplateResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Gallery template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Copy a gallery template into your custom templates","tags":["Template Gallery"]}},"/v1/whatsapp/templates":{"post":{"description":"Create a template and submit it to Meta for review in one call. WhatsApp only lets a business start a conversation with an approved template.\n\n**Approval is asynchronous.** The template comes back `pending`. Poll `GET /whatsapp/templates/{id}` until `status` is `approved` or `rejected`; review usually takes minutes and can take up to 48 hours.\n\n**Marketing and utility** templates carry `components`, in Meta's own format. Headers are text only for now. Placeholders are named in lowercase snake_case (`{{first_name}}`), and those names are the keys you pass in `parameters` when sending.\n\n**Authentication** templates carry `authentication` options instead: Meta fixes the wording, and the one-time code is sent as `parameters.code`.\n\n**Sending:** utility and authentication templates go out through `POST /whatsapp/send`. A marketing template can be created here but is sent as a campaign.\n\n**Retries:** name and language identify a template. Repeating a create that already succeeded returns 409; read it back with `GET /whatsapp/templates?name=`.","operationId":"whatsapp.templates.create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWhatsappTemplateDto"}}}},"responses":{"201":{"description":"Template created and submitted to Meta","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsappTemplateResponseDto"}}}},"400":{"description":"WhatsApp is not connected for this organization, or Meta rejected the template outright","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"A template with this name already exists in this language","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"503":{"description":"The provider is unreachable. Nothing was created — retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Create a WhatsApp template","tags":["WhatsApp Templates"]},"get":{"description":"Newest first. Includes every template in the organization, whether it was created through this API, in the dashboard, or imported from Meta.","operationId":"whatsapp.templates.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["draft","pending","approved","rejected","paused","disabled"]}},{"name":"category","required":false,"in":"query","schema":{"type":"string","enum":["marketing","utility","authentication"]}},{"name":"name","required":false,"in":"query","description":"Exact template name. Returns one template per language it exists in.","schema":{"example":"order_shipped","type":"string"}}],"responses":{"200":{"description":"WhatsApp templates","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsappTemplateListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List WhatsApp templates","tags":["WhatsApp Templates"]}},"/v1/whatsapp/templates/{id}":{"get":{"description":"The template with its current approval status. `status` and `rejection_reason` follow Meta's review, and `category` follows any recategorization by Meta.","operationId":"whatsapp.templates.get","parameters":[{"name":"id","required":true,"in":"path","description":"Template id","schema":{"type":"string"}}],"responses":{"200":{"description":"The template","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsappTemplateResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get a WhatsApp template","tags":["WhatsApp Templates"]},"patch":{"description":"Replace a template's content and resubmit it to Meta. `status` returns to `pending` and `rejection_reason` is cleared. Name, language and category cannot be changed.\n\nOnly `approved` and `rejected` templates can be edited. Authentication templates and templates adopted from Meta's library cannot: their wording belongs to Meta.\n\nMeta limits edits of an approved template to one per 24 hours and ten per 30 days.","operationId":"whatsapp.templates.update","parameters":[{"name":"id","required":true,"in":"path","description":"Template id","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWhatsappTemplateDto"}}}},"responses":{"200":{"description":"Template resubmitted to Meta","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsappTemplateResponseDto"}}}},"400":{"description":"This template cannot be edited, or Meta rejected the edit outright","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"503":{"description":"The provider is unreachable. Nothing was changed — retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Edit a WhatsApp template","tags":["WhatsApp Templates"]},"delete":{"description":"Delete a template here and at Meta. It can no longer be sent. Meta keeps the name of a deleted approved template reserved for 30 days, so creating it again under the same name may be refused until then.","operationId":"whatsapp.templates.remove","parameters":[{"name":"id","required":true,"in":"path","description":"Template id","schema":{"type":"string"}}],"responses":{"200":{"description":"Template deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Delete a WhatsApp template","tags":["WhatsApp Templates"]}},"/v1/email/send":{"post":{"description":"Send a transactional email for time-sensitive notifications.\n\n**Content Options** (mutually exclusive):\n- `html` and/or `text`: Inline email content\n- `template_id`: Use a pre-saved template from your dashboard\n\n**Template Variables:**\nUse `variables` to personalize content with `{{variableName}}` syntax.\n\n**Recipients:** Up to 50 total across `to`, `cc`, and `bcc`. If **any** address is malformed, the entire request is rejected with `422` and no email is sent — matching Amazon SES, which rejects the whole message when a single recipient is invalid. Deliverability (DNS/MX) is validated asynchronously *after* the request is accepted; undeliverable recipients are dropped individually, and if every recipient is undeliverable the email is simply not sent (the `202` was already returned).\n\n**Rate Limit:** 1000 requests/minute per organization.","operationId":"email.send","parameters":[{"name":"Idempotency-Key","in":"header","description":"Optional key (max 256 chars) to make this send idempotent for 24h. Any unique string; a common pattern is <event-type>/<entity-id>.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendEmailDto"}}}},"responses":{"202":{"description":"Email queued for delivery","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResponseDto"}}}},"400":{"description":"Business validation failed — more than 50 total recipients, an unverified sender domain, or a disallowed attachment (the whole request is rejected)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"Idempotency-Key reused with a different payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"413":{"description":"Request body exceeds 30 MB","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"503":{"description":"A request with this Idempotency-Key is still processing — retry shortly","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Send transactional email","tags":["Email"]}},"/v1/email":{"get":{"description":"Retrieve a paginated list of transactional emails sent by your organization.","operationId":"email.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"List of transactional emails","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List transactional emails","tags":["Email"]}},"/v1/email/{id}":{"get":{"description":"Get details of a specific transactional email by ID.","operationId":"email.get","parameters":[{"name":"id","required":true,"in":"path","description":"Transactional email ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Transactional email details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDetailResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Email not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Retrieve a transactional email","tags":["Email"]}},"/v1/sms/send":{"post":{"description":"Send transactional SMS for time-sensitive notifications.\n\n**Phone Number Format:**\n- KSA: `+9665XXXXXXXX` (13 chars with +)\n- Egypt: `+201XXXXXXXXX` (13 chars with +)\n\n**Character Encoding:**\n- GSM (Latin, digits, basic symbols): 160 chars/SMS, 153 chars/part for multi-part\n- Unicode (Arabic, emoji): 70 chars/SMS, 67 chars/part for multi-part\n\n**Template Variables:**\nUse `variables` with `{{variableName}}` syntax.\n\n**Important:** All recipients must be from the same country. Mixing KSA and Egypt numbers in one request is not allowed.\n\n**Rate Limit:** 1000 requests/minute per organization.","operationId":"sms.send","parameters":[{"name":"Idempotency-Key","in":"header","description":"Optional key (max 256 chars) to make this send idempotent for 24h. Any unique string; a common pattern is <event-type>/<entity-id>.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendSmsDto"}}}},"responses":{"202":{"description":"SMS queued for delivery","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"Idempotency-Key reused with a different payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"503":{"description":"A request with this Idempotency-Key is still processing — retry shortly","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Send transactional SMS","tags":["SMS"]}},"/v1/sms":{"get":{"description":"Retrieve a paginated list of transactional SMS sent by your organization.","operationId":"sms.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"List of transactional SMS","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List transactional SMS","tags":["SMS"]}},"/v1/sms/{id}":{"get":{"description":"Get details of a specific transactional SMS by ID.","operationId":"sms.get","parameters":[{"name":"id","required":true,"in":"path","description":"Transactional SMS ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Transactional SMS details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsDetailResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"SMS not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Retrieve a transactional SMS","tags":["SMS"]}},"/v1/contacts":{"post":{"description":"Create a new contact. At least one of `email`, `phone_number` or `external_id` is required.","operationId":"contacts.create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContactDevApiDto"}}}},"responses":{"201":{"description":"Contact created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Create a contact","tags":["Contacts"]},"get":{"description":"Retrieve a paginated list of contacts. Supports search, filtering by list/tag/segment, and sorting.","operationId":"contacts.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}},{"name":"email","required":false,"in":"query","description":"Filter by email address. Exact match, case-insensitive.","schema":{"type":"string"}},{"name":"phone_number","required":false,"in":"query","description":"Filter by phone number. Exact match; accepts separators and bare 966/20 prefixes, which are canonicalized to E.164 before matching.","schema":{"type":"string"}},{"name":"external_id","required":false,"in":"query","description":"Filter by your own identifier. Exact match — the way to reconcile an import against your own records.","schema":{"type":"string"}},{"name":"list_id","required":false,"in":"query","description":"Filter by list ID","schema":{"format":"uuid","type":"string"}},{"name":"tag_id","required":false,"in":"query","description":"Filter by tag ID","schema":{"format":"uuid","type":"string"}},{"name":"segment_id","required":false,"in":"query","description":"Only the members of this segment, as of its last count (`counted_at` on the segment). Returns 409 `segment_not_ready` while the segment is `processing`.","schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"description":"List of contacts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"`segment_id` does not name a segment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"The segment is still `processing` (`segment_not_ready`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List contacts","tags":["Contacts"]}},"/v1/contacts/{id}":{"get":{"description":"Retrieve a contact by ID.","operationId":"contacts.get","parameters":[{"name":"id","required":true,"in":"path","description":"Contact ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Contact details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get a contact","tags":["Contacts"]},"patch":{"description":"Update an existing contact.","operationId":"contacts.update","parameters":[{"name":"id","required":true,"in":"path","description":"Contact ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateContactDevApiDto"}}}},"responses":{"200":{"description":"Contact updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Update a contact","tags":["Contacts"]},"delete":{"description":"Soft-delete a contact by ID.","operationId":"contacts.remove","parameters":[{"name":"id","required":true,"in":"path","description":"Contact ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Contact deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Delete a contact","tags":["Contacts"]}},"/v1/contacts/bulk-delete":{"post":{"description":"Delete up to 1000 contacts at once.","operationId":"contacts.bulkDelete","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkDeleteContactsDevApiDto"}}}},"responses":{"200":{"description":"Contacts deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkDeleteContactsResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Bulk delete contacts","tags":["Contacts"]}},"/v1/lists":{"post":{"description":"Create a new contact list.","operationId":"lists.create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateListDevApiDto"}}}},"responses":{"201":{"description":"List created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Create a list","tags":["Lists"]},"get":{"description":"Retrieve a paginated list of contact lists.","operationId":"lists.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}},{"name":"search","required":false,"in":"query","description":"Search by list name","schema":{"type":"string"}}],"responses":{"200":{"description":"List of lists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List all lists","tags":["Lists"]}},"/v1/lists/{id}":{"get":{"description":"Retrieve a contact list by ID.","operationId":"lists.get","parameters":[{"name":"id","required":true,"in":"path","description":"List ID","schema":{"type":"string"}}],"responses":{"200":{"description":"List details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"List not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get a list","tags":["Lists"]},"patch":{"description":"Update a contact list.","operationId":"lists.update","parameters":[{"name":"id","required":true,"in":"path","description":"List ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateListDevApiDto"}}}},"responses":{"200":{"description":"List updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"List not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Update a list","tags":["Lists"]},"delete":{"description":"Soft-delete a contact list.","operationId":"lists.remove","parameters":[{"name":"id","required":true,"in":"path","description":"List ID","schema":{"type":"string"}}],"responses":{"200":{"description":"List deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"List not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Delete a list","tags":["Lists"]}},"/v1/lists/{id}/contacts":{"post":{"description":"Add contacts to a list by their IDs.","operationId":"lists.contacts.add","parameters":[{"name":"id","required":true,"in":"path","description":"List ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListContactIdsDto"}}}},"responses":{"200":{"description":"Contacts added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListContactsOperationResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"List not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Add contacts to a list","tags":["Lists"]},"delete":{"description":"Remove contacts from a list by their IDs.","operationId":"lists.contacts.remove","parameters":[{"name":"id","required":true,"in":"path","description":"List ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListContactIdsDto"}}}},"responses":{"200":{"description":"Contacts removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListContactsOperationResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"List not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Remove contacts from a list","tags":["Lists"]}},"/v1/tags":{"post":{"description":"Create a new tag. Name must be unique within the organization.","operationId":"tags.create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTagDevApiDto"}}}},"responses":{"201":{"description":"Tag created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"Tag name already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Create a tag","tags":["Tags"]},"get":{"description":"Retrieve a paginated list of tags with contact counts.","operationId":"tags.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}},{"name":"search","required":false,"in":"query","description":"Search by tag name","schema":{"type":"string"}}],"responses":{"200":{"description":"List of tags","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List all tags","tags":["Tags"]}},"/v1/tags/{id}":{"get":{"description":"Retrieve a tag by ID.","operationId":"tags.get","parameters":[{"name":"id","required":true,"in":"path","description":"Tag ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Tag details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Tag not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get a tag","tags":["Tags"]},"patch":{"description":"Update an existing tag.","operationId":"tags.update","parameters":[{"name":"id","required":true,"in":"path","description":"Tag ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTagDevApiDto"}}}},"responses":{"200":{"description":"Tag updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Tag not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"Tag name already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Update a tag","tags":["Tags"]},"delete":{"description":"Permanently delete a tag.","operationId":"tags.remove","parameters":[{"name":"id","required":true,"in":"path","description":"Tag ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Tag deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Tag not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Delete a tag","tags":["Tags"]}},"/v1/tags/{id}/contacts":{"post":{"description":"Add contacts to a tag by their IDs.","operationId":"tags.contacts.add","parameters":[{"name":"id","required":true,"in":"path","description":"Tag ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagContactIdsDto"}}}},"responses":{"200":{"description":"Contacts added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagContactsOperationResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Tag not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Add contacts to a tag","tags":["Tags"]},"delete":{"description":"Remove contacts from a tag by their IDs.","operationId":"tags.contacts.remove","parameters":[{"name":"id","required":true,"in":"path","description":"Tag ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagContactIdsDto"}}}},"responses":{"200":{"description":"Contacts removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagContactsOperationResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Tag not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Remove contacts from a tag","tags":["Tags"]}},"/v1/templates":{"post":{"description":"Create a reusable email template. The HTML is sanitized and unsubscribe placeholders are auto-injected.","operationId":"templates.create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTemplateDevApiDto"}}}},"responses":{"201":{"description":"Template created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Create a template","tags":["Templates"]},"get":{"description":"Retrieve a paginated list of email templates. Excludes campaign-only templates.","operationId":"templates.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}},{"name":"search","required":false,"in":"query","description":"Search by template name","schema":{"type":"string"}}],"responses":{"200":{"description":"List of templates","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List templates","tags":["Templates"]}},"/v1/templates/{id}":{"get":{"description":"Retrieve a template by ID.","operationId":"templates.get","parameters":[{"name":"id","required":true,"in":"path","description":"Template ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Template details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get a template","tags":["Templates"]},"patch":{"description":"Update a template. Provide only the fields you want to change.","operationId":"templates.update","parameters":[{"name":"id","required":true,"in":"path","description":"Template ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTemplateDevApiDto"}}}},"responses":{"200":{"description":"Template updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Update a template","tags":["Templates"]},"delete":{"description":"Soft-delete a template. Campaigns that already reference this template continue to use the snapshot taken at send time.","operationId":"templates.remove","parameters":[{"name":"id","required":true,"in":"path","description":"Template ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Template deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Delete a template","tags":["Templates"]}},"/v1/properties":{"post":{"description":"Define a new custom contact property. `field_key` and `data_type` are immutable after creation.","operationId":"properties.create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePropertyDevApiDto"}}}},"responses":{"201":{"description":"Property created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropertyResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"Property with this field_key already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error or reserved field_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Create a property","tags":["Properties"]},"get":{"description":"Retrieve a paginated list of custom properties for your organization. System properties are not returned.","operationId":"properties.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}},{"name":"search","required":false,"in":"query","description":"Search by field_key or display_name","schema":{"type":"string"}}],"responses":{"200":{"description":"List of properties","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropertyListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List properties","tags":["Properties"]}},"/v1/properties/{id}":{"get":{"description":"Retrieve a property by ID.","operationId":"properties.get","parameters":[{"name":"id","required":true,"in":"path","description":"Property ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Property details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropertyResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Property not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get a property","tags":["Properties"]},"patch":{"description":"`field_key` and `data_type` are immutable. Only `display_name`, `description`, and `fallback_value` can be changed.","operationId":"properties.update","parameters":[{"name":"id","required":true,"in":"path","description":"Property ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePropertyDevApiDto"}}}},"responses":{"200":{"description":"Property updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropertyResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Property not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Update a property","tags":["Properties"]},"delete":{"description":"Soft-delete a property. Existing values stored on contacts are preserved but no longer surfaced through the schema.","operationId":"properties.remove","parameters":[{"name":"id","required":true,"in":"path","description":"Property ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Property deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Property not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Delete a property","tags":["Properties"]}},"/v1/events/send":{"post":{"description":"Ingest a custom event by name.\n\n**Secret API key (`be_…`)** — the event must already be defined for the organization (otherwise 404), and a payload that fails the declared schema is rejected with 422. Identify the contact with at least one of `external_id`, `email` or `phone_number` (several allowed), or with `contact_id` on its own; `contact_id` may not be combined with the others. `anonymous_id` is rejected — a server cannot assert a device-minted id.\n\n**Client key (`pub_…`, from a browser or mobile SDK)** — an unknown event name defines itself, and a schema mismatch is recorded on the event rather than refused, because a shipped client cannot be redeployed to fix a rejection. Identify with at least one of `anonymous_id`, `external_id`, `email` or `phone_number`; `contact_id` is not accepted. Requests carrying an `Origin` header must come from an origin on the organization allowlist.\n\nIdentifiers are ranked `contact_id` > `external_id` > `email` > `phone_number` > `anonymous_id`. The highest-ranked match wins; a matched contact adopts any identifier it does not already hold; a lower-ranked contact is merged in only when nothing stronger identifies it, and an ambiguous match is logged rather than merged. `phone_number` must be E.164 (e.g. +966501234567). An unmatched identifier auto-creates a contact.\n\n**Batch** — send `{ \"events\": [ <item>, ... ] }` (max 50 items, same item shape) instead of a single event. Items are processed independently: a per-item validation error yields `{ \"error\": ... }` in that item's slot of the `results` array and does not fail the batch. The `Idempotency-Key` header applies to the whole request.","operationId":"events.send","parameters":[{"name":"Idempotency-Key","in":"header","description":"Optional key (max 256 chars) to make this send idempotent for 24h. Any unique string; a common pattern is <event-type>/<entity-id>.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"description":"A single event, or a batch envelope { \"events\": [ <same item shape>, ... ] } with up to 50 items.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/IngestEventDto"},{"$ref":"#/components/schemas/IngestEventBatchRequestDto"}]}}}},"responses":{"202":{"description":"Event accepted for processing. A batch returns one result per item.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/EventAcceptedResponseDto"},{"$ref":"#/components/schemas/EventBatchResponseDto"}]}}}},"400":{"description":"No identifier supplied (CONTACT_REQUIRED); contact_id combined with external_id/email/phone_number (CONTACT_AMBIGUOUS); anonymous_id sent with a secret key (ANONYMOUS_ID_NOT_ALLOWED); or contact_id sent with a client key (CONTACT_ID_NOT_ALLOWED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"403":{"description":"Client key used from an origin that is not on the organization allowlist","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Event name not defined for this organization, or contact_id not found. Client keys auto-create the event definition instead","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"Idempotency-Key reused with a different payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Schema mismatch. Secret keys only — a client key records the errors on the event and accepts it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Organization's event ingestion budget for the current 60-second window is exhausted. Counted in events, so a batch spends one per item. Retry after the retry-after header","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"503":{"description":"A request with this Idempotency-Key is still processing — retry shortly","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Send a custom event","tags":["Events"]}},"/v1/events":{"post":{"description":"Define a new custom event. `name` is unique per organization and immutable after creation. Optionally pass a `conversion` object to mark the event as a conversion and map its revenue value/currency.","operationId":"events.create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEventDevApiDto"}}}},"responses":{"201":{"description":"Event created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventResponseDto"}}}},"400":{"description":"Invalid conversion config (e.g. value field is not numeric)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"An event with this name already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Create an event definition","tags":["Events"]},"get":{"description":"Retrieve a cursor-paginated list of event definitions for your organization.","operationId":"events.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}},{"name":"search","required":false,"in":"query","description":"Search by event name (case-insensitive contains).","schema":{"type":"string"}}],"responses":{"200":{"description":"List of events","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List event definitions","tags":["Events"]}},"/v1/events/{id}":{"get":{"description":"Retrieve an event definition by ID.","operationId":"events.get","parameters":[{"name":"id","required":true,"in":"path","description":"Event ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Event details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get an event definition","tags":["Events"]},"patch":{"description":"`name` is immutable. `description`, `schema`, and `conversion` can be changed. Omit `conversion` to leave it unchanged; `enabled: false` clears it.","operationId":"events.update","parameters":[{"name":"id","required":true,"in":"path","description":"Event ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEventDevApiDto"}}}},"responses":{"200":{"description":"Event updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventResponseDto"}}}},"400":{"description":"Invalid conversion config (e.g. value field is not numeric)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error (e.g. value_field missing when enabling)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Update an event definition","tags":["Events"]},"delete":{"description":"Soft-delete an event definition. Returns 409 EVENT_IN_USE if an active automation flow references it.","operationId":"events.remove","parameters":[{"name":"id","required":true,"in":"path","description":"Event ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Event deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"Event is referenced by an active automation flow","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Delete an event definition","tags":["Events"]}},"/v1/in-app/campaigns":{"post":{"description":"Create an in-app campaign in Draft status, then put it in front of devices with `POST /v1/in-app/campaigns/{id}/activate`.","operationId":"inApp.campaigns.create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInAppCampaignDevApiDto"}}}},"responses":{"201":{"description":"Campaign created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InAppCampaignIdResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Create an in-app campaign","tags":["In-App Campaigns"]},"get":{"description":"Retrieve a cursor-paginated list of in-app campaigns. Supports search and status filter.","operationId":"inApp.campaigns.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}},{"name":"search","required":false,"in":"query","description":"Search by campaign name","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["draft","scheduled","active","paused","ended","archived"]}}],"responses":{"200":{"description":"List of campaigns","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InAppCampaignListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List in-app campaigns","tags":["In-App Campaigns"]}},"/v1/in-app/campaigns/{id}":{"get":{"description":"Retrieve an in-app campaign by ID.","operationId":"inApp.campaigns.get","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InAppCampaignResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get an in-app campaign","tags":["In-App Campaigns"]},"patch":{"description":"Update a campaign. Unlike push, an Active campaign is editable — there is no moment of dispatch to be \"after\", and devices pick the change up on their next bundle fetch. Only Ended and Archived are frozen.","operationId":"inApp.campaigns.update","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateInAppCampaignDevApiDto"}}}},"responses":{"200":{"description":"Campaign updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InAppCampaignIdResponseDto"}}}},"400":{"description":"Campaign cannot be edited in its current status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Update an in-app campaign","tags":["In-App Campaigns"]},"delete":{"description":"Soft-delete an in-app campaign.","operationId":"inApp.campaigns.remove","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Delete an in-app campaign","tags":["In-App Campaigns"]}},"/v1/in-app/campaigns/{id}/activate":{"post":{"description":"Put the campaign in front of devices. There is no fan-out and no queue job — activation flips the status the bundle builder reads, and devices collect the campaign on their next foreground sync.\n\nSegment scoping needs no pre-pass: membership is evaluated against the asking contact each time a device fetches its bundle.","operationId":"inApp.campaigns.activate","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign activated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InAppCampaignResponseDto"}}}},"400":{"description":"Campaign is Ended or Archived, its end date is already past, or it names a segment that no longer exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Activate an in-app campaign","tags":["In-App Campaigns"]}},"/v1/in-app/campaigns/{id}/pause":{"post":{"description":"Stop showing it, keeping every impression counter so it resumes where it left off. Devices holding a cached bundle may still draw it until their next fetch.","operationId":"inApp.campaigns.pause","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign paused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InAppCampaignResponseDto"}}}},"400":{"description":"Only an Active campaign can be paused","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Pause an in-app campaign","tags":["In-App Campaigns"]}},"/v1/in-app/campaigns/{id}/end":{"post":{"description":"Terminal — an Ended campaign cannot be reactivated, clone it instead. Devices drop it on their next bundle fetch.","operationId":"inApp.campaigns.end","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign ended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InAppCampaignResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"End an in-app campaign","tags":["In-App Campaigns"]}},"/v1/in-app/campaigns/{id}/clone":{"post":{"description":"Copy a campaign into a fresh Draft.","operationId":"inApp.campaigns.clone","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign cloned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InAppCampaignIdResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Clone an in-app campaign","tags":["In-App Campaigns"]}},"/v1/push/campaigns":{"post":{"description":"Create a push campaign in Draft status. Target it with `list_ids`, `tag_ids` and/or `segment_ids`, then send it with `POST /v1/push/campaigns/{id}/send`.","operationId":"push.campaigns.create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePushCampaignDevApiDto"}}}},"responses":{"201":{"description":"Campaign created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushCampaignIdResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Create a push campaign","tags":["Push Campaigns"]},"get":{"description":"Retrieve a paginated list of push campaigns. Supports search and status filter.","operationId":"push.campaigns.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}},{"name":"search","required":false,"in":"query","description":"Search by campaign name","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["draft","scheduled","queued","sending","paused","sent","failed","cancelled","archived"]}}],"responses":{"200":{"description":"List of campaigns","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushCampaignListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List push campaigns","tags":["Push Campaigns"]}},"/v1/push/campaigns/{id}":{"get":{"description":"Retrieve a push campaign by ID.","operationId":"push.campaigns.get","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushCampaignResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get a push campaign","tags":["Push Campaigns"]},"patch":{"description":"Update a campaign. To change the send time of a Scheduled campaign, call send again with a new `scheduled_at`.","operationId":"push.campaigns.update","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePushCampaignDevApiDto"}}}},"responses":{"200":{"description":"Campaign updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushCampaignIdResponseDto"}}}},"400":{"description":"Campaign cannot be updated in its current status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Update a push campaign","tags":["Push Campaigns"]},"delete":{"description":"Soft-delete a push campaign.","operationId":"push.campaigns.remove","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Delete a push campaign","tags":["Push Campaigns"]}},"/v1/push/campaigns/{id}/send":{"post":{"description":"Queue the campaign for fan-out. Pass `scheduled_at` (future ISO 8601) to schedule, or omit it to send now.\n\nEvery recipient passes the same push consent gate as a single send — a contact who opted out in-app, or whose devices are all dead, is skipped rather than refused, so a partially-reachable audience still sends.","operationId":"push.campaigns.send","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"Optional key (max 256 chars) that makes a retry of this request safe for 24h: a repeat returns the first response instead of acting twice.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendPushCampaignDevApiDto"}}}},"responses":{"200":{"description":"Campaign queued or scheduled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushCampaignIdResponseDto"}}}},"400":{"description":"Campaign not ready to send, audience is empty, invalid schedule time, or no push app configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"Idempotency-Key reused with a different payload or for a different campaign","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded, or the monthly push quota is exhausted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"503":{"description":"A request with this Idempotency-Key is still processing — retry shortly","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Send or schedule a push campaign","tags":["Push Campaigns"]}},"/v1/push/campaigns/{id}/cancel":{"post":{"description":"Cancel a Scheduled, Queued or Sending campaign.","operationId":"push.campaigns.cancel","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"Optional key (max 256 chars) that makes a retry of this request safe for 24h: a repeat returns the first response instead of acting twice.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushCampaignIdResponseDto"}}}},"400":{"description":"Campaign cannot be cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"Idempotency-Key reused with a different payload or for a different campaign","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"503":{"description":"A request with this Idempotency-Key is still processing — retry shortly","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Cancel a push campaign","tags":["Push Campaigns"]}},"/v1/push/devices":{"post":{"description":"Upsert on `(organization, installation_id)`. Safe to call on every app launch — a repeat call refreshes the token and device attributes.\n\n**Identity:** name the contact with `contact_id` on its own, or one/both of `email` and `phone_number`. The contact must already exist — registering a device never creates one.\n\n**Credentials:** `device_token` for android/ios; `endpoint` + `p256dh_key` + `auth_key` for web.\n\n**Opt-out is durable:** a device revoked by `POST /v1/push/devices/unregister` or by the user in-app stays revoked, and the response reports `opted_out: true`. Registration is not consent.","operationId":"push.devices.register","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterPushDeviceDevApiDto"}}}},"responses":{"200":{"description":"Device registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterPushDeviceResponseDto"}}}},"400":{"description":"Missing transport credentials, or an ambiguous contact reference","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"No contact matches the supplied reference","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Register or refresh a device and bind it to a contact","tags":["Push Devices"]}},"/v1/push/devices/bulk":{"post":{"description":"The migration path. Same item shape and the same `(organization, installation_id)` upsert as `POST /v1/push/devices`, processed asynchronously.\n\n**Shape errors come back now, everything else is queued.** A row that fails validation is reported inline in `errors` and is never enqueued. The rest are accepted and processed in the background, so a large import is never bounded by a request timeout.\n\n**Contacts are resolved by the full identifier ladder** — `external_id`, then `email`, then `phone_number` — so an export keyed on your own user id works directly, and duplicate contacts consolidate instead of forking. A row whose identifiers match more than one irreconcilable contact is reported rather than guessed at.\n\n**An import never creates a contact.** Import contacts first, then devices, then apply opt-outs with `POST /v1/push/devices/unregister`.\n\n**Poll `GET /v1/push/imports/{id}`** for status, counts and the per-row failures in `errors`.\n\nRate-limited separately from the rest of the API: 120 calls per minute.","operationId":"push.devices.registerBulk","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkRegisterPushDevicesDevApiDto"}}}},"responses":{"202":{"description":"Batch accepted for processing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkRegisterPushDevicesResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"The envelope itself is invalid — empty, or over the item cap","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Import up to 1000 devices","tags":["Push Devices"]}},"/v1/push/imports/{id}":{"get":{"description":"`queued` and `processing` mean keep polling. `completed` means every accepted row was attempted — a non-zero `failed` is normal and the reasons are in `errors`, one entry per failed row carrying the `index` it held in your submitted array. `failed` means the job itself died, which is not the same as its rows failing.","operationId":"push.imports.get","parameters":[{"name":"id","required":true,"in":"path","description":"The `job_id` from the import","schema":{"type":"string"}}],"responses":{"200":{"description":"Import status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushDeviceImportStatusResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"No such import job in this organization","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Status of a device import","tags":["Push Devices"]}},"/v1/push/devices/unregister":{"post":{"description":"Durable opt-out recorded as `contact_opt_out`. A later `POST /v1/push/devices` for the same `installation_id` will NOT resurrect it — registration runs on every app launch, so a revocation those calls could undo would be no opt-out at all.\n\nA reinstall is a different case: it mints a new `installation_id` and registers as a new device. What carries the opt-out across a reinstall is the contact-level push consent — revoking a contact's last active device also marks the contact unsubscribed from push, and that is never reversed automatically. The exception is a contact who still holds another active device: there the reinstalled handset becomes reachable again, and you should call this endpoint again if it must stay off.\n\nModelled as POST rather than DELETE because the installation id travels in the body and DELETE bodies are dropped by a fair number of HTTP clients and proxies.","operationId":"push.devices.unregister","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnregisterPushDeviceDevApiDto"}}}},"responses":{"200":{"description":"Device revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushDeviceRevokedResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"No such device in this organization","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Revoke a device","tags":["Push Devices"]}},"/v1/push/contacts/{contact_id}/devices":{"get":{"description":"The raw `device_token` is never returned — it is a sendable credential. Correlate on `installation_id`, which is the natural key on every push route.","operationId":"push.devices.listForContact","parameters":[{"name":"contact_id","required":true,"in":"path","description":"Arsel contact UUID","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}},{"name":"platform","required":false,"in":"query","schema":{"type":"string","enum":["android","ios","web"]}},{"name":"status","required":false,"in":"query","description":"Defaults to every status, revoked and expired included.","schema":{"type":"string","enum":["active","expired","revoked","failed"]}}],"responses":{"200":{"description":"Devices for this contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushDeviceListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Contact not found in this organization","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List a contact's push devices","tags":["Push Devices"]}},"/v1/push/send":{"post":{"description":"Addresses a contact, never a device token: an anonymous registration is stored but is not addressable. Every ACTIVE device the contact has registered receives the notification.\n\nPush has no transactional exemption. Unlike email and SMS, there is no statutory carve-out to invoke: consent is a single explicit OS-level grant, the OS enforces it whatever a sender labels the message, and Apple 4.5.4 makes marketing the category that needs extra opt-in. A contact who has opted out of push is refused — send essential service messages over email or SMS instead.\n\nA contact with no active device is a successful skip, not an error: the 202 means accepted for processing, and per-device outcomes land in push analytics.","operationId":"push.send","parameters":[{"name":"Idempotency-Key","in":"header","description":"Optional key (max 256 chars) to make this send idempotent for 24h. Any unique string; a common pattern is <event-type>/<entity-id>.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendPushDevApiDto"}}}},"responses":{"202":{"description":"Push queued for delivery","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"403":{"description":"Contact is not reachable on push (opted out, or no deliverable device)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"No contact matches the supplied reference","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"Idempotency-Key reused with a different payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded, or the monthly push quota is exhausted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"503":{"description":"A request with this Idempotency-Key is still processing — retry shortly","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Send a push notification to one contact","tags":["Push"]}},"/v1/push":{"get":{"description":"Most recent first. Campaign sends are not included — this lists only pushes sent through this API.","operationId":"push.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"Transactional push notifications","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List transactional push notifications","tags":["Push"]}},"/v1/push/{id}":{"get":{"description":"How many of the contact's devices were targeted and how many the push provider accepted.","operationId":"push.get","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"The push notification","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushDetailResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Push notification not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get a transactional push notification","tags":["Push"]}},"/v1/whatsapp/send":{"post":{"description":"Send a one-off WhatsApp message from an approved template — an OTP, an order update, a receipt.\n\n**Templates are required.** WhatsApp does not allow business-initiated free-text messages. Create a template, wait for Meta to approve it, then send it by name.\n\n**Categories behave differently:**\n- `AUTHENTICATION` — pass the one-time code as `parameters.code`. The code is repeated automatically into the copy-code button when the template has one.\n- `UTILITY` / `MARKETING` — pass one entry per `{{placeholder}}` in the template body, keyed by placeholder name.\n\n**Delivery is asynchronous.** A 202 means the provider accepted the message, not that it was delivered. Poll `GET /whatsapp/{id}` for the delivery state, which updates as the provider reports it.\n\n**Billing:** charged per message at the rate for the destination country and category, held on acceptance and settled from the delivery receipt. A message the provider rejects is not charged.","operationId":"whatsapp.send","parameters":[{"name":"Idempotency-Key","in":"header","description":"Optional key (max 256 chars) to make this send idempotent for 24h. Any unique string; a common pattern is <event-type>/<entity-id>. Strongly recommended for OTPs, where a retry that sends a second code invalidates the first.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendWhatsappDto"}}}},"responses":{"202":{"description":"Accepted by the provider","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResponseDto"}}}},"400":{"description":"The number, the template or the WhatsApp setup was rejected. Deterministic — the same request will fail the same way, so do not retry it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"403":{"description":"Recipient opted out, monthly limit reached, or insufficient WhatsApp balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"Idempotency-Key reused with a different payload, or the template name is approved in more than one language and `language` was omitted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"503":{"description":"The provider is unreachable or rate limiting the account. The message was not sent — retry it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Send a transactional WhatsApp message","tags":["WhatsApp"]}},"/v1/whatsapp":{"get":{"description":"Most recent first. Campaign sends are not included — this lists only messages sent through this API.","operationId":"whatsapp.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"Transactional WhatsApp messages","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsappListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List transactional WhatsApp messages","tags":["WhatsApp"]}},"/v1/whatsapp/{id}":{"get":{"description":"Current delivery state. `status` advances from queued through sent, delivered and read as the provider reports it.","operationId":"whatsapp.get","parameters":[{"name":"id","required":true,"in":"path","description":"Message id returned by /whatsapp/send","schema":{"type":"string"}}],"responses":{"200":{"description":"The message","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsappDetailResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Message not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get a transactional WhatsApp message","tags":["WhatsApp"]}},"/v1/segments":{"post":{"description":"Create a segment from a rule tree. The members are computed in the background: the segment is returned with `status: processing` and becomes `ready` once they are.","operationId":"segments.create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSegmentDevApiDto"}}}},"responses":{"201":{"description":"Segment created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Invalid body or filter, or the organization has reached its segment limit (`segment_limit_reached`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Create a segment","tags":["Segments"]},"get":{"description":"Retrieve a paginated list of segments, most recent first. Segments are dynamic audiences defined by rules.","operationId":"segments.list","parameters":[{"name":"limit","required":false,"in":"query","description":"Number of items to return per page (1-100).","schema":{"minimum":1,"maximum":100,"default":20,"type":"number"}},{"name":"after","required":false,"in":"query","description":"Return items after this ID (exclusive). Use the `id` of the last item from the previous page.","schema":{"type":"string"}},{"name":"before","required":false,"in":"query","description":"Return items before this ID (exclusive). Use the `id` of the first item from the previous page.","schema":{"type":"string"}},{"name":"search","required":false,"in":"query","description":"Search by segment name","schema":{"type":"string"}}],"responses":{"200":{"description":"List of segments","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentListResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"List all segments","tags":["Segments"]}},"/v1/segments/{id}":{"get":{"description":"Retrieve a segment by ID.","operationId":"segments.get","parameters":[{"name":"id","required":true,"in":"path","description":"Segment ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Segment details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Segment not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get a segment","tags":["Segments"]},"patch":{"description":"Update a segment. Sending `filter` replaces the whole rule tree and recomputes the members in the background; changing only the name or description leaves them as they are.","operationId":"segments.update","parameters":[{"name":"id","required":true,"in":"path","description":"Segment ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSegmentDevApiDto"}}}},"responses":{"200":{"description":"Segment updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SegmentResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Segment not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"422":{"description":"Invalid body or filter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Update a segment","tags":["Segments"]},"delete":{"description":"Soft-delete a segment. Returns 409 `segment_in_use` while a scheduled or sending campaign targets it.","operationId":"segments.remove","parameters":[{"name":"id","required":true,"in":"path","description":"Segment ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Segment deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletedResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Segment not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"409":{"description":"A scheduled or sending campaign targets the segment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Delete a segment","tags":["Segments"]}},"/v1/usage":{"get":{"description":"Retrieve the current period's usage against the organization's plan limits. The period is the UTC calendar month, not the billing cycle.","operationId":"usage.get","parameters":[],"responses":{"200":{"description":"Usage for the current period","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get usage","tags":["Usage"]}},"/v1/email/campaigns/{id}/stats":{"get":{"description":"Delivery, engagement, per-link clicks and attributed conversions for an email campaign. Totals cover the whole life of the campaign. Counts of distinct contacts can trail the event counts by up to 15 minutes. Until a campaign is sent its event counts are 0 and its rates are `null`.","operationId":"email.campaigns.stats","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign stats","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCampaignStatsResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get email campaign stats","tags":["CampaignStatsDevApi","Email Campaigns"]}},"/v1/sms/campaigns/{id}/stats":{"get":{"description":"Delivery status counts, clicks and attributed conversions for an SMS campaign. Totals cover the whole life of the campaign. Counts of distinct contacts can trail the event counts by up to 15 minutes. Until a campaign is sent its event counts are 0 and its rates are `null`.","operationId":"sms.campaigns.stats","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign stats","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsCampaignStatsResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get SMS campaign stats","tags":["CampaignStatsDevApi","SMS Campaigns"]}},"/v1/push/campaigns/{id}/stats":{"get":{"description":"Delivery, display, engagement and attributed conversions for a push campaign. `delivered` means the push service accepted the notification; `confirmed` is the subset a device reported receiving. Totals cover the whole life of the campaign. Counts of distinct contacts can trail the event counts by up to 15 minutes. Until a campaign is sent its event counts are 0 and its rates are `null`.","operationId":"push.campaigns.stats","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign stats","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushCampaignStatsResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get push campaign stats","tags":["CampaignStatsDevApi","Push Campaigns"]}},"/v1/in-app/campaigns/{id}/stats":{"get":{"description":"Impressions and engagement for an in-app campaign. Nothing is sent on this channel, so there is no delivery or failure count. Totals cover the whole life of the campaign. Counts of distinct contacts can trail the event counts by up to 15 minutes. Until a campaign is sent its event counts are 0 and its rates are `null`.","operationId":"inApp.campaigns.stats","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign stats","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InAppCampaignStatsResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get in-app campaign stats","tags":["CampaignStatsDevApi","In-App Campaigns"]}},"/v1/whatsapp/campaigns/{id}/stats":{"get":{"description":"Delivery, reads, button taps and attributed conversions for a WhatsApp campaign. Totals cover the whole life of the campaign. Counts of distinct contacts can trail the event counts by up to 15 minutes. Until a campaign is sent its event counts are 0 and its rates are `null`.","operationId":"whatsapp.campaigns.stats","parameters":[{"name":"id","required":true,"in":"path","description":"Campaign ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Campaign stats","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsappCampaignStatsResponseDto"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"404":{"description":"Campaign not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}},"429":{"description":"Rate limit exceeded. Retry after the `retry-after` header.","headers":{"retry-after":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevApiErrorResponseDto"}}}}},"security":[{"apiKey":[]}],"summary":"Get WhatsApp campaign stats","tags":["CampaignStatsDevApi","WhatsApp Campaigns"]}}},"info":{"title":"Arsel Developer API","description":"\nDeveloper API for programmatic access to Arsel.\n\n## Authentication\nAll endpoints require an API key in the Authorization header:\n```\nAuthorization: Bearer be_your_api_key_here\n```\n\nGenerate API keys from your organization settings in the dashboard.\n\n## Rate Limits\n- **1000 requests per minute** per organization\n- Rate limit headers included in every response:\n  - `ratelimit-limit`: Maximum requests per window\n  - `ratelimit-remaining`: Requests remaining\n  - `ratelimit-reset`: Seconds until window resets\n- HTTP 429 returned when limit exceeded, with `retry-after` header\n\n## Response Format\n- **202 Accepted**: Request queued successfully, returns message ID\n- **401 Unauthorized**: Invalid or missing API key\n- **422 Unprocessable Entity**: Validation error\n- **429 Too Many Requests**: Rate limit exceeded\n      ","version":"1.0","contact":{}},"tags":[],"servers":[{"url":"https://api.arsel.sa"}],"components":{"securitySchemes":{"apiKey":{"scheme":"bearer","bearerFormat":"JWT","type":"http","description":"API Key (format: be_...)"}},"schemas":{"DevApiErrorResponseDto":{"type":"object","properties":{"status_code":{"type":"number","example":422},"name":{"type":"string","enum":["bad_request","unauthorized","forbidden","not_found","conflict","payload_too_large","validation_error","rate_limit_exceeded","internal_server_error","upstream_error","service_unavailable","upstream_timeout","error","monthly_quota_exceeded","sending_suspended","attachment_rejected","invalid_idempotency_key","invalid_idempotent_request","concurrent_idempotent_requests","anonymous_id_not_allowed","contact_ambiguous","contact_id_not_allowed","contact_not_found","contact_required","conversion_currency_field_invalid","conversion_currency_field_required","conversion_currency_required","conversion_currency_unsupported","conversion_currency_without_value","conversion_value_field_invalid","domain_limit_reached","domain_unavailable","event_in_use","event_limit_reached","event_not_found","schema_mismatch","segment_in_use","segment_limit_reached","segment_not_ready","invalid_recipient","re_engagement_required","recipient_rate_limited","account_rate_limited","template_not_found","template_name_duplicate","template_content_rejected","template_parameter_mismatch","template_disabled","authentication_failed","authorization_failed","policy_violation","invalid_request","provider_unavailable","transport_error","unknown"],"example":"validation_error","description":"Machine-readable error name. Branch on this, not on `message`."},"message":{"type":"string","example":"Missing required field: to"}},"required":["status_code","name","message"]},"CreateEmailCampaignDevApiDto":{"type":"object","properties":{"name":{"type":"string","description":"Campaign name","example":"Summer Sale 2026"},"subject":{"type":"string","description":"Email subject line","example":"50% Off Everything!"},"preheader":{"type":"string","description":"Email preheader text","example":"Limited time offer"},"from_name":{"type":"string","description":"Sender display name","example":"Acme Store"},"from":{"type":"string","description":"Sender email address. Must belong to a verified domain.","example":"marketing@acme.com"},"reply_to":{"type":"string","description":"Reply-to email address","example":"support@acme.com"},"template_id":{"type":"string","format":"uuid","description":"Template ID to link to this campaign","example":"0192a1b2-c3d4-7e5f-9000-0000000000a1"},"list_ids":{"items":{"type":"string","format":"uuid"},"description":"List IDs for recipients","example":["0192a1b2-c3d4-7e5f-9000-0000000000b1"],"type":"array"},"tag_ids":{"items":{"type":"string","format":"uuid"},"description":"Tag IDs for recipients","example":["0192a1b2-c3d4-7e5f-9000-0000000000c1"],"type":"array"},"segment_ids":{"items":{"type":"string","format":"uuid"},"description":"Segment IDs for recipients","example":["0192a1b2-c3d4-7e5f-9000-0000000000d1"],"type":"array"}}},"EmailCampaignIdResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Campaign ID","example":"0192a1b2-c3d4-7e5f-9000-abcdef123456"}},"required":["id"]},"EmailCampaignResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"0192a1b2-c3d4-7e5f-9000-abcdef123456"},"name":{"type":"string","example":"Summer Sale 2026"},"subject":{"type":"string","nullable":true,"example":"50% Off Everything!"},"preheader":{"type":"string","nullable":true,"example":"Limited time offer"},"from_name":{"type":"string","nullable":true,"example":"Acme Store"},"from":{"type":"string","nullable":true,"example":"marketing@acme.com"},"reply_to":{"type":"string","nullable":true,"example":"support@acme.com"},"status":{"type":"string","enum":["draft","scheduled","queued","sending","paused","sent","failed","cancelled","archived"],"example":"draft"},"template_id":{"type":"string","nullable":true,"format":"uuid","example":"0192a1b2-c3d4-7e5f-9000-0000000000a1"},"scheduled_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-04-01T09:00:00.000Z"},"sent_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-04-01T09:05:00.000Z"},"created_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"}},"required":["id","name","subject","preheader","from_name","from","reply_to","status","template_id","scheduled_at","sent_at","created_at","updated_at"]},"EmailCampaignListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/EmailCampaignResponseDto"}}},"required":["object","has_more","data"]},"UpdateEmailCampaignDevApiDto":{"type":"object","properties":{"name":{"type":"string","example":"Summer Sale 2026"},"subject":{"type":"string","example":"50% Off Everything!"},"preheader":{"type":"string","example":"Limited time offer"},"from_name":{"type":"string","example":"Acme Store"},"from":{"type":"string","example":"marketing@acme.com"},"reply_to":{"type":"string","example":"support@acme.com"},"template_id":{"type":"string","format":"uuid","example":"0192a1b2-c3d4-7e5f-9000-0000000000a1"},"list_ids":{"items":{"type":"string","format":"uuid"},"example":["0192a1b2-c3d4-7e5f-9000-0000000000b1"],"type":"array"},"tag_ids":{"items":{"type":"string","format":"uuid"},"example":["0192a1b2-c3d4-7e5f-9000-0000000000c1"],"type":"array"},"segment_ids":{"items":{"type":"string","format":"uuid"},"example":["0192a1b2-c3d4-7e5f-9000-0000000000d1"],"type":"array"}}},"DeletedResponseDto":{"type":"object","properties":{"object":{"type":"string","description":"The type of object that was deleted.","example":"contact"},"id":{"type":"string","format":"uuid","example":"0192a1b2-c3d4-7e5f-9000-abcdef123456"},"deleted":{"type":"boolean","description":"Always true.","example":true}},"required":["object","id","deleted"]},"SendEmailCampaignDevApiDto":{"type":"object","properties":{"scheduled_at":{"type":"string","format":"date-time","description":"ISO 8601 date-time to schedule the campaign for. Omit to send immediately. Must be in the future.","example":"2026-05-01T09:00:00Z"}}},"RegisterEmailDomainDevApiDto":{"type":"object","properties":{"name":{"type":"string","example":"mail.acme.com","maxLength":255,"description":"The domain to send from. Stored lowercased."}},"required":["name"]},"EmailDomainRecordDto":{"type":"object","properties":{"type":{"type":"string","example":"CNAME","description":"DNS record type."},"name":{"type":"string","example":"abc123._domainkey.mail.acme.com"},"value":{"type":"string","example":"abc123.dkim.amazonses.com"},"purpose":{"type":"string","enum":["dkim","spf","dmarc","mail_from"],"example":"dkim"},"required":{"type":"boolean","example":true,"description":"The domain cannot become `verified` until every required record is published. The others are recommended for deliverability."},"status":{"type":"string","enum":["pending","verified"],"example":"pending"}},"required":["type","name","value","purpose","required","status"]},"EmailDomainDetailResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"0192a1b2-c3d4-7e5f-9000-abcdef123456"},"name":{"type":"string","example":"mail.acme.com"},"status":{"type":"string","enum":["pending","verified","failed"],"example":"verified","description":"Only a `verified` domain can send: any address at it is accepted as `from`."},"verified_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-03-08T12:00:00.000Z"},"created_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"},"records":{"description":"The DNS records to publish at the domain's DNS provider.","type":"array","items":{"$ref":"#/components/schemas/EmailDomainRecordDto"}}},"required":["id","name","status","verified_at","created_at","records"]},"EmailDomainResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"0192a1b2-c3d4-7e5f-9000-abcdef123456"},"name":{"type":"string","example":"mail.acme.com"},"status":{"type":"string","enum":["pending","verified","failed"],"example":"verified","description":"Only a `verified` domain can send: any address at it is accepted as `from`."},"verified_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-03-08T12:00:00.000Z"},"created_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"}},"required":["id","name","status","verified_at","created_at"]},"EmailDomainListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/EmailDomainResponseDto"}}},"required":["object","has_more","data"]},"CreateSmsCampaignDevApiDto":{"type":"object","properties":{"name":{"type":"string","description":"Campaign name","example":"Eid Sale Announcement"},"description":{"type":"string","description":"Campaign description","example":"SMS blast for Eid promotion"},"content":{"type":"string","description":"SMS message content (max 3200 chars)","example":"Hello! Check out our Eid sale at..."},"from":{"type":"string","description":"Sender name (3-11 chars: letters, numbers, - and _; must be pre-approved)","example":"AcmeStore"},"list_ids":{"items":{"type":"string","format":"uuid"},"description":"List IDs for recipients","example":["0192a1b2-c3d4-7e5f-9000-0000000000b1"],"type":"array"},"tag_ids":{"items":{"type":"string","format":"uuid"},"description":"Tag IDs for recipients","example":["0192a1b2-c3d4-7e5f-9000-0000000000c1"],"type":"array"},"segment_ids":{"items":{"type":"string","format":"uuid"},"description":"Segment IDs for recipients","example":["0192a1b2-c3d4-7e5f-9000-0000000000d1"],"type":"array"}},"required":["name"]},"SmsCampaignIdResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Campaign ID","example":"0192a1b2-c3d4-7e5f-9000-abcdef123456"}},"required":["id"]},"SmsCampaignResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"0192a1b2-c3d4-7e5f-9000-abcdef123456"},"name":{"type":"string","example":"Eid Sale Announcement"},"description":{"type":"string","nullable":true,"example":"SMS blast for Eid promotion"},"content":{"type":"string","nullable":true,"example":"Hello! Check out our Eid sale at..."},"from":{"type":"string","nullable":true,"example":"AcmeStore"},"country":{"type":"string","nullable":true,"example":"KSA"},"status":{"type":"string","enum":["draft","scheduled","queued","sending","paused","sent","failed","cancelled","archived"],"example":"draft"},"num_segments":{"type":"number","description":"Number of SMS segments this message will use (matches Twilio's num_segments).","example":1},"scheduled_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-04-01T09:00:00.000Z"},"sent_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-04-01T09:05:00.000Z"},"created_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"}},"required":["id","name","description","content","from","country","status","scheduled_at","sent_at","created_at","updated_at"]},"SmsCampaignListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/SmsCampaignResponseDto"}}},"required":["object","has_more","data"]},"UpdateSmsCampaignDevApiDto":{"type":"object","properties":{"name":{"type":"string","example":"Eid Sale Announcement"},"description":{"type":"string","example":"SMS blast for Eid promotion"},"content":{"type":"string","example":"Hello! Check out our Eid sale at..."},"from":{"type":"string","example":"AcmeStore"},"list_ids":{"items":{"type":"string","format":"uuid"},"example":["0192a1b2-c3d4-7e5f-9000-0000000000b1"],"type":"array"},"tag_ids":{"items":{"type":"string","format":"uuid"},"example":["0192a1b2-c3d4-7e5f-9000-0000000000c1"],"type":"array"},"segment_ids":{"items":{"type":"string","format":"uuid"},"example":["0192a1b2-c3d4-7e5f-9000-0000000000d1"],"type":"array"}}},"SendSmsCampaignDevApiDto":{"type":"object","properties":{"scheduled_at":{"type":"string","format":"date-time","description":"ISO 8601 date-time to schedule the campaign for. Omit to send immediately. Must be in the future.","example":"2026-05-01T09:00:00Z"}}},"SmsSenderResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"0192a1b2-c3d4-7e5f-9000-abcdef123456"},"sender_name":{"type":"string","example":"AcmeStore","description":"The value to pass as `from` when sending SMS."},"country":{"type":"string","example":"KSA","description":"The country this sender is approved to send to, as SMS campaigns report it."},"purposes":{"type":"array","example":["transactional","marketing"],"description":"What the sender is registered for. Campaigns need `marketing`; messages sent through `POST /v1/sms/send` need `transactional`.","items":{"type":"string","enum":["transactional","marketing"]}},"created_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"}},"required":["id","sender_name","country","purposes","created_at"]},"SmsSenderListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/SmsSenderResponseDto"}}},"required":["object","has_more","data"]},"GalleryCategoryItemDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"},"name":{"type":"string","example":"Newsletter"}},"required":["id","name"]},"GalleryCategoriesResponseDto":{"type":"object","properties":{"types":{"type":"array","items":{"$ref":"#/components/schemas/GalleryCategoryItemDto"}},"seasons":{"type":"array","items":{"$ref":"#/components/schemas/GalleryCategoryItemDto"}},"features":{"type":"array","items":{"$ref":"#/components/schemas/GalleryCategoryItemDto"}},"industries":{"type":"array","items":{"$ref":"#/components/schemas/GalleryCategoryItemDto"}}},"required":["types","seasons","features","industries"]},"GalleryListItemResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"},"name":{"type":"string","example":"Modern Welcome Email"},"has_amp":{"type":"boolean","example":false},"created_at":{"type":"string","format":"date-time","example":"2026-01-15T10:00:00.000Z"}},"required":["id","name","has_amp","created_at"]},"GalleryListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/GalleryListItemResponseDto"}}},"required":["object","has_more","data"]},"GalleryDetailResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"},"name":{"type":"string","example":"Modern Welcome Email"},"has_amp":{"type":"boolean","example":false},"created_at":{"type":"string","format":"date-time","example":"2026-01-15T10:00:00.000Z"},"html":{"type":"string","description":"Full rendered HTML body. Use this as `html` when creating a custom template via `POST /templates`.","example":"<html>...</html>"},"template_types":{"type":"array","items":{"$ref":"#/components/schemas/GalleryCategoryItemDto"}},"template_seasons":{"type":"array","items":{"$ref":"#/components/schemas/GalleryCategoryItemDto"}},"template_features":{"type":"array","items":{"$ref":"#/components/schemas/GalleryCategoryItemDto"}},"template_industries":{"type":"array","items":{"$ref":"#/components/schemas/GalleryCategoryItemDto"}}},"required":["id","name","has_amp","created_at","html","template_types","template_seasons","template_features","template_industries"]},"CopyGalleryTemplateDevApiDto":{"type":"object","properties":{"name":{"type":"string","description":"Name for the new custom template. If omitted, defaults to the gallery template's name.","example":"Spring Sale 2026"}}},"CopyGalleryTemplateResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"ID of the newly-created custom template.","example":"0192a1b2-c3d4-7e5f-9000-abcdef123456"}},"required":["id"]},"WaTemplateNamedParamDto":{"type":"object","properties":{"param_name":{"type":"string"},"example":{"type":"string"}},"required":["param_name","example"]},"WaTemplateExampleDto":{"type":"object","properties":{"header_text":{"type":"array","items":{"type":"string"}},"header_handle":{"type":"array","items":{"type":"string"}},"body_text":{"type":"array","items":{"type":"array","items":{"type":"string"}}},"header_text_named_params":{"type":"array","items":{"$ref":"#/components/schemas/WaTemplateNamedParamDto"}},"body_text_named_params":{"type":"array","items":{"$ref":"#/components/schemas/WaTemplateNamedParamDto"}}}},"WaTemplateButtonDto":{"type":"object","properties":{"type":{"type":"string","enum":["QUICK_REPLY","URL","PHONE_NUMBER"]},"text":{"type":"string"},"url":{"type":"string"},"phone_number":{"type":"string"}},"required":["type","text"]},"WaTemplateComponentDto":{"type":"object","properties":{"type":{"type":"string","enum":["HEADER","BODY","FOOTER","BUTTONS"]},"format":{"type":"string","enum":["TEXT","IMAGE","VIDEO","DOCUMENT"]},"text":{"type":"string"},"example":{"$ref":"#/components/schemas/WaTemplateExampleDto"},"buttons":{"type":"array","items":{"$ref":"#/components/schemas/WaTemplateButtonDto"}}},"required":["type"]},"WhatsappTemplateSupportedAppDto":{"type":"object","properties":{"package_name":{"type":"string","example":"com.example.myapp"},"signature_hash":{"type":"string","description":"Base64 SHA-256 hash of the app signing key."}},"required":["package_name","signature_hash"]},"WhatsappTemplateAuthenticationDto":{"type":"object","properties":{"otp_type":{"type":"string","enum":["copy_code","one_tap","zero_tap"],"description":"How the recipient gets the code off the screen.","example":"copy_code"},"add_security_recommendation":{"type":"boolean","description":"Append \"For your security, do not share this code.\" to the body."},"code_expiration_minutes":{"type":"number","description":"Adds a footer reading \"This code expires in N minutes.\"","minimum":1,"maximum":90},"button_text":{"type":"string","description":"Button label. Meta supplies a localized default.","maxLength":25},"autofill_text":{"type":"string","description":"Label shown while autofilling. One-tap and zero-tap only.","maxLength":25},"supported_apps":{"description":"Android apps allowed to autofill the code. Required for one-tap and zero-tap.","type":"array","items":{"$ref":"#/components/schemas/WhatsappTemplateSupportedAppDto"}},"zero_tap_terms_accepted":{"type":"boolean","description":"Must be true for zero-tap: accepts Meta's zero-tap terms."},"message_send_ttl_seconds":{"type":"number","description":"How long Meta may keep retrying delivery before dropping the code.","minimum":30,"maximum":900}},"required":["otp_type"]},"CreateWhatsappTemplateDto":{"type":"object","properties":{"name":{"type":"string","description":"Template name: lowercase letters, numbers and underscores, starting with a letter. Unique per language.","example":"order_shipped","maxLength":512},"language":{"type":"string","description":"Meta language code.","example":"en","maxLength":10},"category":{"type":"string","enum":["marketing","utility","authentication"],"example":"utility"},"components":{"description":"Required for `marketing` and `utility`; not accepted for `authentication`. The template content in Meta's own component format, passed through unchanged: `HEADER` (format `TEXT` only), `BODY`, `FOOTER` and `BUTTONS`. Placeholders are named, in lowercase snake_case: `{{first_name}}`.","example":[{"type":"HEADER","format":"TEXT","text":"Your order"},{"type":"BODY","text":"Hi {{first_name}}, order {{order_id}} has shipped."},{"type":"FOOTER","text":"Arsel Store"},{"type":"BUTTONS","buttons":[{"type":"URL","text":"Track","url":"https://example.com/track"}]}],"type":"array","items":{"$ref":"#/components/schemas/WaTemplateComponentDto"}},"authentication":{"description":"Required when `category` is `authentication`. Meta fixes the wording of an authentication template, so you choose options instead of writing components.","allOf":[{"$ref":"#/components/schemas/WhatsappTemplateAuthenticationDto"}]}},"required":["name","language","category"]},"WhatsappTemplateResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"0199b1c2-c3d4-7e5f-9000-abcdef123456"},"name":{"type":"string","example":"order_shipped","description":"The value to pass as `template_name` when sending."},"language":{"type":"string","example":"en"},"category":{"type":"string","enum":["marketing","utility","authentication"],"example":"utility","description":"Can change after submission: Meta recategorizes templates, and the category sets the price per message."},"status":{"type":"string","enum":["draft","pending","approved","rejected","paused","disabled"],"example":"pending","description":"Only `approved` templates can be sent. `pending` is under review by Meta; `paused` and `disabled` are applied by Meta for low quality; `draft` means the status is not known yet."},"rejection_reason":{"type":"string","nullable":true,"description":"Meta's reason, when the template was rejected.","example":null},"parameters":{"example":["first_name","order_id"],"description":"The keys `POST /whatsapp/send` needs in `parameters` for this template. `[\"code\"]` for an authentication template.","type":"array","items":{"type":"string"}},"components":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"The template content in Meta's own component format, as stored.","example":[{"type":"HEADER","format":"TEXT","text":"Your order"},{"type":"BODY","text":"Hi {{first_name}}, order {{order_id}} has shipped."},{"type":"FOOTER","text":"Arsel Store"},{"type":"BUTTONS","buttons":[{"type":"URL","text":"Track","url":"https://example.com/track"}]}]},"created_at":{"type":"string","format":"date-time","example":"2026-10-07T09:00:00.000Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-10-07T09:00:00.000Z"}},"required":["id","name","language","category","status","rejection_reason","parameters","components","created_at","updated_at"]},"WhatsappTemplateListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/WhatsappTemplateResponseDto"}}},"required":["object","has_more","data"]},"UpdateWhatsappTemplateDto":{"type":"object","properties":{"components":{"description":"The full replacement content. The template content in Meta's own component format, passed through unchanged: `HEADER` (format `TEXT` only), `BODY`, `FOOTER` and `BUTTONS`. Placeholders are named, in lowercase snake_case: `{{first_name}}`.","example":[{"type":"HEADER","format":"TEXT","text":"Your order"},{"type":"BODY","text":"Hi {{first_name}}, order {{order_id}} has shipped."},{"type":"FOOTER","text":"Arsel Store"},{"type":"BUTTONS","buttons":[{"type":"URL","text":"Track","url":"https://example.com/track"}]}],"type":"array","items":{"$ref":"#/components/schemas/WaTemplateComponentDto"}}},"required":["components"]},"AttachmentDto":{"type":"object","properties":{"filename":{"type":"string","description":"Filename with extension","example":"invoice.pdf"},"content":{"type":"string","description":"Base64-encoded file content. All attachments combined must not exceed 30 MB decoded.","example":"JVBERi0xLjQK..."},"content_type":{"type":"string","description":"MIME type of the attachment. For a calendar invite use `text/calendar; method=REQUEST` (or `CANCEL`) so mail clients show accept/decline.","example":"application/pdf"},"content_id":{"type":"string","description":"Sends the file inline, for an image referenced from the HTML as `<img src=\"cid:logo\">`.","example":"logo"}},"required":["filename","content"]},"SendEmailDto":{"type":"object","properties":{"from":{"type":"string","description":"Sender email address (must be a verified domain)","example":"noreply@yourdomain.com"},"from_name":{"type":"string","description":"Sender display name","example":"Your Company"},"to":{"description":"Recipient email address(es)","example":["user@example.com"],"type":"array","items":{"type":"string"}},"subject":{"type":"string","description":"Email subject line. Required for inline html/text sends; optional with template_id (inherited from the template when omitted).","example":"Your Order Confirmation"},"html":{"type":"string","description":"HTML content of the email. Mutually exclusive with template_id.","example":"<html><body><h1>Hello {{name}}</h1></body></html>"},"text":{"type":"string","description":"Plain text content of the email. Can be used alone or with html.","example":"Hello {{name}}"},"template_id":{"type":"string","format":"uuid","description":"Template ID to use. Mutually exclusive with html/text.","example":"0192a1b2-c3d4-7e5f-9000-0000000000a1"},"variables":{"type":"object","additionalProperties":true,"description":"Variables to replace in the template or html/text content using {{key}} syntax","example":{"name":"John","orderNumber":"12345"}},"reply_to":{"type":"string","description":"Reply-to email address","example":"support@yourdomain.com"},"cc":{"description":"CC recipient email addresses","example":["manager@example.com"],"type":"array","items":{"type":"string"}},"bcc":{"description":"BCC recipient email addresses","example":["archive@example.com"],"type":"array","items":{"type":"string"}},"attachments":{"description":"File attachments (max 10 files, max 30 MB total decoded)","type":"array","items":{"$ref":"#/components/schemas/AttachmentDto"}},"category":{"type":"string","description":"Category for analytics grouping","example":"order-confirmation"}},"required":["from","from_name","to"]},"SendResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Unique message identifier. Use this to track delivery status.","example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"}},"required":["id"]},"EmailStatusSummaryDto":{"type":"object","properties":{"sent":{"type":"number","example":2},"delivered":{"type":"number","example":2},"opened":{"type":"number","example":1},"clicked":{"type":"number","example":0},"bounced":{"type":"number","example":0},"complained":{"type":"number","example":0},"rejected":{"type":"number","example":0},"unsubscribed":{"type":"number","example":0},"render_failure":{"type":"number","example":0}},"required":["sent","delivered","opened","clicked","bounced","complained","rejected","unsubscribed","render_failure"]},"EmailListItemResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Unique transactional email identifier","example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"},"from":{"type":"string","example":"hello@example.com"},"from_name":{"type":"string","nullable":true,"example":"My App"},"subject":{"type":"string","example":"Welcome!","nullable":true},"category":{"type":"string","nullable":true,"example":"otp"},"status":{"$ref":"#/components/schemas/EmailStatusSummaryDto"},"created_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"}},"required":["id","from","from_name","subject","category","status","created_at"]},"EmailListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/EmailListItemResponseDto"}}},"required":["object","has_more","data"]},"EmailRecipientStatusDto":{"type":"object","properties":{"email":{"type":"string","example":"user@example.com"},"status":{"type":"string","example":"delivered","enum":["sent","delivered","opened","clicked","bounced","complained","rejected","unsubscribed","render_failure"]},"timestamp":{"type":"string","nullable":true,"format":"date-time","example":"2026-03-08T12:01:00.000Z"}},"required":["email","status","timestamp"]},"EmailAttachmentSummaryDto":{"type":"object","properties":{"filename":{"type":"string","example":"invoice.pdf"},"size":{"type":"number","description":"Size in bytes.","example":12345},"content_type":{"type":"string","example":"application/pdf"}},"required":["filename","size","content_type"]},"EmailDetailResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Unique transactional email identifier","example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"},"from":{"type":"string","example":"hello@example.com"},"from_name":{"type":"string","nullable":true,"example":"My App"},"to":{"description":"Per-recipient status for the To recipients","type":"array","items":{"$ref":"#/components/schemas/EmailRecipientStatusDto"}},"cc":{"description":"Per-recipient status for the Cc recipients","type":"array","items":{"$ref":"#/components/schemas/EmailRecipientStatusDto"}},"bcc":{"description":"Per-recipient status for the Bcc recipients","type":"array","items":{"$ref":"#/components/schemas/EmailRecipientStatusDto"}},"subject":{"type":"string","example":"Welcome!","nullable":true},"html":{"type":"string","description":"HTML content (null if content has been purged)","nullable":true},"text":{"type":"string","description":"Plain text content (null if content has been purged)","nullable":true},"reply_to":{"type":"string","nullable":true,"example":"noreply@example.com"},"category":{"type":"string","nullable":true,"example":"otp"},"attachments":{"nullable":true,"type":"array","items":{"$ref":"#/components/schemas/EmailAttachmentSummaryDto"}},"created_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"}},"required":["id","from","from_name","to","cc","bcc","subject","html","text","reply_to","category","attachments","created_at"]},"SendSmsDto":{"type":"object","properties":{"from":{"type":"string","description":"Sender name (must be pre-approved for the target country). Alphanumeric, 3-11 characters.","example":"Basics"},"to":{"description":"Recipient phone number(s) in international format. KSA: +9665XXXXXXXX. Egypt: +201XXXXXXXXX. Max 100 per request.","example":["+201234567890"],"type":"array","items":{"type":"string"}},"content":{"type":"string","description":"Message content. Supports {{variableName}} syntax. GSM: 160 chars/part. Unicode: 70 chars/part. Max 10 parts.","example":"Hello {{name}}, your OTP is {{otp}}"},"variables":{"type":"object","additionalProperties":true,"description":"Variables to replace in the content using {{key}} syntax","example":{"name":"John","otp":"123456"}},"category":{"type":"string","description":"Category for analytics grouping","example":"verification-otp"}},"required":["from","to","content"]},"SmsStatusSummaryDto":{"type":"object","properties":{"sent":{"type":"number","example":2},"delivered":{"type":"number","example":2},"undelivered":{"type":"number","example":0},"blocked":{"type":"number","example":0},"rejected":{"type":"number","example":0}},"required":["sent","delivered","undelivered","blocked","rejected"]},"SmsListItemResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Unique transactional SMS identifier","example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"},"from":{"type":"string","example":"MyApp"},"content":{"type":"string","description":"SMS content (null if content has been purged)","example":"Your OTP is 123456","nullable":true},"country":{"type":"string","example":"KSA"},"message_parts":{"type":"number","example":1},"category":{"type":"string","nullable":true,"example":"otp"},"status":{"$ref":"#/components/schemas/SmsStatusSummaryDto"},"created_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"}},"required":["id","from","content","country","message_parts","category","status","created_at"]},"SmsListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/SmsListItemResponseDto"}}},"required":["object","has_more","data"]},"SmsRecipientStatusDto":{"type":"object","properties":{"phone":{"type":"string","example":"+966512345678"},"status":{"type":"string","example":"delivered","enum":["sent","delivered","undelivered","buffered","blocked","rejected","expired","clicked","unknown","failed"]},"timestamp":{"type":"string","nullable":true,"format":"date-time","example":"2026-03-08T12:01:00.000Z"}},"required":["phone","status","timestamp"]},"SmsDetailResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Unique transactional SMS identifier","example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"},"from":{"type":"string","example":"MyApp"},"to":{"description":"Per-recipient delivery status","type":"array","items":{"$ref":"#/components/schemas/SmsRecipientStatusDto"}},"content":{"type":"string","description":"SMS content (null if content has been purged)","example":"Your OTP is 123456","nullable":true},"country":{"type":"string","example":"KSA"},"message_parts":{"type":"number","example":1},"category":{"type":"string","nullable":true,"example":"otp"},"created_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"}},"required":["id","from","to","content","country","message_parts","category","created_at"]},"CreateContactDevApiDto":{"type":"object","properties":{"email":{"type":"string","description":"Contact email address","example":"john@example.com"},"phone_number":{"type":"string","description":"Contact phone number (E.164 format)","example":"+966512345678"},"external_id":{"type":"string","description":"Your own identifier for this person. Unique per organization, and the identifier to key a migration on — it ranks above email and phone when a client SDK event resolves this contact.","example":"user_10294"},"first_name":{"type":"string","description":"First name","example":"John"},"last_name":{"type":"string","description":"Last name","example":"Doe"},"properties":{"type":"object","additionalProperties":true,"description":"Custom properties as key-value pairs","example":{"company":"Acme","city":"Riyadh"}},"list_ids":{"items":{"type":"string","format":"uuid"},"description":"List IDs to add the contact to","example":["0192a1b2-c3d4-7e5f-9000-0000000000b1"],"type":"array"}}},"ContactResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"0192a1b2-c3d4-7e5f-9000-abcdef123456"},"email":{"type":"string","nullable":true,"example":"john@example.com"},"phone_number":{"type":"string","nullable":true,"example":"+966512345678"},"external_id":{"type":"string","nullable":true,"example":"user_10294"},"first_name":{"type":"string","nullable":true,"example":"John"},"last_name":{"type":"string","nullable":true,"example":"Doe"},"source":{"type":"string","example":"manual"},"properties":{"type":"object","additionalProperties":true,"example":{"company":"Acme"},"description":"Custom properties as key-value pairs"},"is_suppressed":{"type":"boolean","example":false},"created_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"}},"required":["id","email","phone_number","external_id","first_name","last_name","source","properties","is_suppressed","created_at","updated_at"]},"ContactListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/ContactResponseDto"}}},"required":["object","has_more","data"]},"UpdateContactDevApiDto":{"type":"object","properties":{"email":{"type":"string","nullable":true,"example":"john@example.com"},"phone_number":{"type":"string","nullable":true,"example":"+966512345678"},"external_id":{"type":"string","nullable":true,"example":"user_10294","description":"Your own identifier for this person. Unique per organization."},"first_name":{"type":"string","nullable":true,"example":"John"},"last_name":{"type":"string","nullable":true,"example":"Doe"},"properties":{"type":"object","additionalProperties":true,"example":{"company":"Acme"},"description":"Custom properties as key-value pairs"},"list_ids":{"items":{"type":"string","format":"uuid"},"example":["0192a1b2-c3d4-7e5f-9000-0000000000b1"],"type":"array"}}},"BulkDeleteContactsDevApiDto":{"type":"object","properties":{"contact_ids":{"items":{"type":"string","format":"uuid"},"description":"Contact IDs to delete (max 1000)","example":["0192a1b2-c3d4-7e5f-9000-abcdef123456","0192a1b2-c3d4-7e5f-9000-abcdef123457"],"type":"array"}},"required":["contact_ids"]},"BulkDeleteContactsResponseDto":{"type":"object","properties":{"deleted":{"type":"number","description":"Contacts actually deleted.","example":3}},"required":["deleted"]},"CreateListDevApiDto":{"type":"object","properties":{"name":{"type":"string","description":"List name","example":"Newsletter Subscribers"},"description":{"type":"string","description":"List description","example":"Monthly newsletter recipients"}},"required":["name"]},"ListResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"0192a1b2-c3d4-7e5f-9000-abcdef123456"},"name":{"type":"string","example":"Newsletter Subscribers"},"description":{"type":"string","nullable":true,"example":"Monthly newsletter recipients"},"contact_count":{"type":"number","example":150},"created_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"}},"required":["id","name","description","contact_count","created_at","updated_at"]},"ListListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/ListResponseDto"}}},"required":["object","has_more","data"]},"UpdateListDevApiDto":{"type":"object","properties":{"name":{"type":"string","example":"Newsletter Subscribers"},"description":{"type":"string","example":"Monthly newsletter recipients"}}},"ListContactIdsDto":{"type":"object","properties":{"contact_ids":{"items":{"type":"string","format":"uuid"},"description":"Contact IDs to add/remove","example":["0192a1b2-c3d4-7e5f-9000-abcdef123456","0192a1b2-c3d4-7e5f-9000-abcdef123457"],"type":"array"}},"required":["contact_ids"]},"ListContactsOperationResponseDto":{"type":"object","properties":{"message":{"type":"string","example":"Contacts added to list"},"processed":{"type":"number","example":5},"skipped":{"type":"number","example":2}},"required":["message","processed","skipped"]},"CreateTagDevApiDto":{"type":"object","properties":{"name":{"type":"string","description":"Tag name","example":"VIP"},"description":{"type":"string","description":"Tag description","example":"High-value customers"}},"required":["name"]},"TagResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"0192a1b2-c3d4-7e5f-9000-abcdef123456"},"name":{"type":"string","example":"VIP"},"description":{"type":"string","nullable":true,"example":"High-value customers"},"contact_count":{"type":"number","example":42},"created_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"}},"required":["id","name","description","contact_count","created_at","updated_at"]},"TagListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/TagResponseDto"}}},"required":["object","has_more","data"]},"UpdateTagDevApiDto":{"type":"object","properties":{"name":{"type":"string","example":"VIP"},"description":{"type":"string","example":"High-value customers"}}},"TagContactIdsDto":{"type":"object","properties":{"contact_ids":{"items":{"type":"string","format":"uuid"},"description":"Contact IDs to add/remove","example":["0192a1b2-c3d4-7e5f-9000-abcdef123456","0192a1b2-c3d4-7e5f-9000-abcdef123457"],"type":"array"}},"required":["contact_ids"]},"TagContactsOperationResponseDto":{"type":"object","properties":{"message":{"type":"string","example":"Contacts added to tag"},"processed":{"type":"number","example":5},"skipped":{"type":"number","example":2}},"required":["message","processed","skipped"]},"CreateTemplateDevApiDto":{"type":"object","properties":{"name":{"type":"string","description":"Template name","example":"Welcome Email v2"},"html":{"type":"string","description":"Email body HTML. Will be sanitized server-side; unsubscribe placeholders are auto-injected.","example":"<h1>Welcome!</h1><p>Thanks for signing up.</p>"}},"required":["name","html"]},"TemplateResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"},"name":{"type":"string","example":"Welcome Email v2"},"html":{"type":"string","description":"Sanitized HTML body. May differ from what was submitted.","example":"<h1>Welcome!</h1>"},"created_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"}},"required":["id","name","html","created_at","updated_at"]},"TemplateListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/TemplateResponseDto"}}},"required":["object","has_more","data"]},"UpdateTemplateDevApiDto":{"type":"object","properties":{"name":{"type":"string","example":"Welcome Email v3"},"html":{"type":"string","description":"Replace the email body HTML.","example":"<h1>Welcome back!</h1>"}}},"CreatePropertyDevApiDto":{"type":"object","properties":{"field_key":{"type":"string","description":"Internal identifier used in `contact.properties`. Lowercase letters, digits, and underscores only. Immutable after creation.","example":"lifetime_value"},"display_name":{"type":"string","description":"Human-readable display name shown in the dashboard.","example":"Lifetime Value"},"data_type":{"type":"string","description":"Data type of values stored under this property. Immutable after creation.","enum":["string","number","boolean","date"],"default":"string"},"description":{"type":"string","description":"Optional description shown in the dashboard.","example":"Aggregate revenue from this contact across all orders."},"fallback_value":{"type":"string","description":"Default value used in merge tags when a contact has no value for this property.","example":"0"}},"required":["field_key","display_name"]},"PropertyResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"},"field_key":{"type":"string","example":"lifetime_value"},"display_name":{"type":"string","example":"Lifetime Value"},"data_type":{"type":"string","enum":["string","number","boolean","date"],"example":"number"},"description":{"type":"string","nullable":true,"example":"Aggregate revenue from this contact."},"fallback_value":{"type":"string","nullable":true,"example":"0"},"created_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"}},"required":["id","field_key","display_name","data_type","description","fallback_value","created_at","updated_at"]},"PropertyListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/PropertyResponseDto"}}},"required":["object","has_more","data"]},"UpdatePropertyDevApiDto":{"type":"object","properties":{"display_name":{"type":"string","example":"Lifetime Revenue"},"description":{"type":"string","nullable":true,"description":"Set to `null` to clear.","example":"Updated description."},"fallback_value":{"type":"string","nullable":true,"description":"Set to `null` to clear.","example":"0"}}},"IngestEventDto":{"type":"object","properties":{"event":{"type":"string","description":"Name of a defined event for the organization.","example":"user.signed_up"},"email":{"type":"string","description":"Email of the contact this event is about. Provide at least one of external_id, email or phone_number; contact_id is mutually exclusive with them. If no contact with this email exists, one is auto-created when the automation runs."},"contact_id":{"type":"string","format":"uuid","description":"Contact UUID this event is about. Mutually exclusive with external_id, email and phone_number."},"phone_number":{"type":"string","description":"Phone number (E.164, e.g. +966501234567) of the contact this event is about. Provide at least one of external_id, email or phone_number (several allowed); contact_id is mutually exclusive with them. If no contact matches, one is auto-created when the automation runs."},"anonymous_id":{"type":"string","description":"Client-generated anonymous identifier. Client-key requests only — a value matching no contact creates an anonymous contact. Sending it with a secret key returns 400."},"external_id":{"type":"string","description":"Your own identifier for this person. Ranks above email and phone, so it is the identifier to key a migration on. A contact matched by email or phone adopts it; if nothing matches, the auto-created contact carries it."},"data":{"type":"object","additionalProperties":true,"description":"Event payload. Validated against the event’s declared schema."},"timestamp":{"type":"string","format":"date-time","description":"ISO-8601 timestamp the event occurred. Defaults to server time."}},"required":["event","data"]},"IngestEventBatchRequestDto":{"type":"object","properties":{"events":{"minItems":1,"maxItems":50,"type":"array","items":{"$ref":"#/components/schemas/IngestEventDto"}}},"required":["events"]},"EventAcceptedResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["accepted"]}},"required":["id","status"]},"EventBatchResponseDto":{"type":"object","properties":{"results":{"type":"array","description":"One entry per submitted event, in request order.","items":{"oneOf":[{"$ref":"#/components/schemas/EventAcceptedResponseDto"},{"$ref":"#/components/schemas/EventBatchItemFailedDto"}]}}},"required":["results"]},"EventBatchItemErrorDto":{"type":"object","properties":{"status_code":{"type":"number","example":422},"code":{"type":"string","example":"contact_required"},"message":{"type":"string"}},"required":["status_code","message"]},"EventBatchItemFailedDto":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/EventBatchItemErrorDto"}},"required":["error"]},"EventFieldDevApiDto":{"type":"object","properties":{"name":{"type":"string","description":"Field identifier. Letters, digits, and underscores; must start with a letter or underscore.","example":"plan"},"type":{"type":"string","enum":["string","number","boolean","date"],"example":"string"},"required":{"type":"boolean","description":"Whether this field must be present in the event payload.","example":true}},"required":["name","type","required"]},"EventSchemaDevApiDto":{"type":"object","properties":{"fields":{"type":"array","items":{"$ref":"#/components/schemas/EventFieldDevApiDto"}}},"required":["fields"]},"ConversionConfigDevApiDto":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether this event counts as a conversion."},"value_field":{"type":"string","nullable":true,"description":"Schema field holding the revenue value. Required when `enabled` is true; send `null` for a count-only conversion.","example":"total"},"currency_field":{"type":"string","nullable":true,"description":"Schema field holding each order’s ISO-4217 currency. Required when more than one currency is accepted.","example":"currency"},"currencies":{"description":"Accepted ISO-4217 currencies. One = fixed; many = read per order from `currency_field`.","example":["SAR","USD"],"type":"array","items":{"type":"string"}}},"required":["enabled"]},"CreateEventDevApiDto":{"type":"object","properties":{"name":{"type":"string","description":"Unique event name for your organization. Immutable after creation.","example":"order.completed"},"description":{"type":"string","description":"Optional human-readable description.","example":"Fired when a customer completes checkout."},"schema":{"description":"Field schema that ingested payloads are validated against. Defaults to no fields.","allOf":[{"$ref":"#/components/schemas/EventSchemaDevApiDto"}]},"conversion":{"description":"Mark this event as a conversion and map its revenue value/currency. Omit for a non-conversion event.","allOf":[{"$ref":"#/components/schemas/ConversionConfigDevApiDto"}]}},"required":["name"]},"ConversionResponseDto":{"type":"object","properties":{"enabled":{"type":"boolean","example":true},"value_field":{"type":"string","example":"total","nullable":true},"currency_field":{"type":"string","example":"currency","nullable":true},"currencies":{"example":["SAR","USD"],"nullable":true,"type":"array","items":{"type":"string"}}},"required":["enabled","value_field","currency_field","currencies"]},"EventResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"},"name":{"type":"string","example":"order.completed"},"description":{"type":"string","example":"Fired when a customer completes checkout.","nullable":true},"schema":{"$ref":"#/components/schemas/EventSchemaDevApiDto"},"conversion":{"$ref":"#/components/schemas/ConversionResponseDto"},"created_at":{"type":"string","format":"date-time","example":"2026-06-01T12:00:00.000Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-06-01T12:00:00.000Z"}},"required":["id","name","description","schema","conversion","created_at","updated_at"]},"EventListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/EventResponseDto"}}},"required":["object","has_more","data"]},"UpdateEventDevApiDto":{"type":"object","properties":{"description":{"type":"string","description":"Updated description. `name` cannot be changed.","example":"Updated description."},"schema":{"$ref":"#/components/schemas/EventSchemaDevApiDto"},"conversion":{"description":"Set or clear conversion config. Omit to leave it unchanged; `enabled: false` clears it.","allOf":[{"$ref":"#/components/schemas/ConversionConfigDevApiDto"}]}}},"DevApiInAppButtonDto":{"type":"object","properties":{"button_id":{"type":"string","description":"Stable across edits; CLICKED engagements report against it.","maxLength":64},"label":{"type":"string","maxLength":40},"action":{"type":"string","enum":["deep_link","url","dismiss","custom_event"]},"value":{"type":"string","nullable":true,"description":"The URL or deep link to open, or the event name for `custom_event`. Unused by `dismiss`."},"style":{"type":"string","enum":["primary","secondary"]}},"required":["button_id","label","action","style"]},"DevApiInAppDisplayRulesDto":{"type":"object","properties":{"max_per_session":{"type":"number","minimum":1,"maximum":10},"max_lifetime":{"type":"number","minimum":1,"maximum":100},"min_seconds_between":{"type":"number","minimum":0,"maximum":2592000},"delay_seconds":{"type":"number","minimum":0,"maximum":300,"description":"Wait this long after the trigger before showing the message."}},"required":["max_per_session","max_lifetime","min_seconds_between","delay_seconds"]},"DevApiInAppContentDto":{"type":"object","properties":{"headline":{"type":"string","maxLength":120},"body":{"type":"string","maxLength":500},"image_url":{"type":"string","format":"uri","nullable":true,"description":"https only."},"background_color":{"type":"string","nullable":true,"example":"#FFFFFF"},"text_color":{"type":"string","nullable":true,"example":"#111111"},"show_close_button":{"type":"boolean","description":"A message with no close button needs a button that dismisses it."}},"required":["headline","body","show_close_button"]},"DevApiInAppTriggerDto":{"type":"object","properties":{"type":{"type":"string","enum":["app_open","screen_view","custom_event"]},"event_name":{"type":"string","nullable":true,"description":"The screen or event name. Required for `screen_view` and `custom_event`, rejected for `app_open`."},"properties":{"type":"object","additionalProperties":{"type":"string"},"nullable":true,"description":"Equality predicates on the event properties, all of which must match. Values are strings."}},"required":["type"]},"CreateInAppCampaignDevApiDto":{"type":"object","properties":{"description":{"type":"string","example":"Onboarding nudge for trial users"},"buttons":{"description":"Up to 2 buttons. Every non-dismiss button needs a `value`.","type":"array","items":{"$ref":"#/components/schemas/DevApiInAppButtonDto"}},"display_rules":{"description":"Frequency caps. Evaluated on the device, so a cap can be exceeded by one across a bundle refresh.","allOf":[{"$ref":"#/components/schemas/DevApiInAppDisplayRulesDto"}]},"priority":{"type":"number","description":"Higher wins when several messages are eligible at once.","minimum":0,"maximum":1000},"target_platforms":{"type":"array","items":{"type":"string","enum":["android","ios","web"]}},"list_ids":{"items":{"type":"string","format":"uuid"},"description":"List IDs to target.","type":"array"},"tag_ids":{"items":{"type":"string","format":"uuid"},"description":"Tag IDs to target.","type":"array"},"segment_ids":{"items":{"type":"string","format":"uuid"},"description":"Segment IDs to target. Membership is evaluated against each contact when their device fetches its bundle, so an edit to the segment takes effect on the next fetch.","type":"array"},"grant_only":{"type":"boolean","description":"Reachable only through an automation grant, ignoring list/tag/segment scoping."},"starts_at":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00Z"},"ends_at":{"type":"string","format":"date-time","description":"Omit for an open-ended campaign.","example":"2026-09-30T09:00:00Z"},"name":{"type":"string","example":"Trial day-3 nudge"},"layout":{"type":"string","enum":["modal","banner_top","banner_bottom","fullscreen","image_only","half_interstitial","alert","form","rating","custom_html"],"description":"How the message is drawn."},"content":{"$ref":"#/components/schemas/DevApiInAppContentDto"},"trigger":{"$ref":"#/components/schemas/DevApiInAppTriggerDto"}},"required":["name","layout","content","trigger"]},"InAppCampaignIdResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Campaign ID","example":"0192a1b2-c3d4-7e5f-9000-abcdef123456"}},"required":["id"]},"InAppCampaignResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"0192a1b2-c3d4-7e5f-9000-abcdef123456"},"name":{"type":"string","example":"Trial day-3 nudge"},"description":{"type":"string","nullable":true,"example":"Onboarding nudge for trial users"},"layout":{"type":"string","enum":["modal","banner_top","banner_bottom","fullscreen","image_only","half_interstitial","alert","form","rating","custom_html"]},"content":{"$ref":"#/components/schemas/DevApiInAppContentDto"},"buttons":{"nullable":true,"type":"array","items":{"$ref":"#/components/schemas/DevApiInAppButtonDto"}},"trigger":{"$ref":"#/components/schemas/DevApiInAppTriggerDto"},"display_rules":{"$ref":"#/components/schemas/DevApiInAppDisplayRulesDto"},"priority":{"type":"number","example":100},"target_platforms":{"type":"array","nullable":true,"items":{"type":"string","enum":["android","ios","web"]}},"list_ids":{"nullable":true,"items":{"type":"string","format":"uuid"},"type":"array"},"tag_ids":{"nullable":true,"items":{"type":"string","format":"uuid"},"type":"array"},"segment_ids":{"nullable":true,"items":{"type":"string","format":"uuid"},"type":"array"},"grant_only":{"type":"boolean","example":false},"status":{"type":"string","enum":["draft","scheduled","active","paused","ended","archived"],"example":"draft"},"starts_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-09-01T09:00:00.000Z"},"ends_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-09-30T09:00:00.000Z"},"activated_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-09-01T09:00:03.000Z"},"ended_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-09-30T09:00:00.000Z"},"created_at":{"type":"string","format":"date-time","example":"2026-08-10T12:00:00.000Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-08-10T12:00:00.000Z"}},"required":["id","name","description","layout","content","buttons","trigger","display_rules","priority","target_platforms","list_ids","tag_ids","segment_ids","grant_only","status","starts_at","ends_at","activated_at","ended_at","created_at","updated_at"]},"InAppCampaignListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/InAppCampaignResponseDto"}}},"required":["object","has_more","data"]},"UpdateInAppCampaignDevApiDto":{"type":"object","properties":{"description":{"type":"string","example":"Onboarding nudge for trial users"},"buttons":{"description":"Up to 2 buttons. Every non-dismiss button needs a `value`.","type":"array","items":{"$ref":"#/components/schemas/DevApiInAppButtonDto"}},"display_rules":{"description":"Frequency caps. Evaluated on the device, so a cap can be exceeded by one across a bundle refresh.","allOf":[{"$ref":"#/components/schemas/DevApiInAppDisplayRulesDto"}]},"priority":{"type":"number","description":"Higher wins when several messages are eligible at once.","minimum":0,"maximum":1000},"target_platforms":{"type":"array","items":{"type":"string","enum":["android","ios","web"]}},"list_ids":{"items":{"type":"string","format":"uuid"},"description":"List IDs to target.","type":"array"},"tag_ids":{"items":{"type":"string","format":"uuid"},"description":"Tag IDs to target.","type":"array"},"segment_ids":{"items":{"type":"string","format":"uuid"},"description":"Segment IDs to target. Membership is evaluated against each contact when their device fetches its bundle, so an edit to the segment takes effect on the next fetch.","type":"array"},"grant_only":{"type":"boolean","description":"Reachable only through an automation grant, ignoring list/tag/segment scoping."},"starts_at":{"type":"string","format":"date-time","example":"2026-09-01T09:00:00Z"},"ends_at":{"type":"string","format":"date-time","description":"Omit for an open-ended campaign.","example":"2026-09-30T09:00:00Z"},"name":{"type":"string","example":"Trial day-3 nudge"},"layout":{"type":"string","enum":["modal","banner_top","banner_bottom","fullscreen","image_only","half_interstitial","alert","form","rating","custom_html"],"description":"How the message is drawn."},"content":{"$ref":"#/components/schemas/DevApiInAppContentDto"},"trigger":{"$ref":"#/components/schemas/DevApiInAppTriggerDto"}}},"DevApiPushActionButtonDto":{"type":"object","properties":{"action_id":{"type":"string","description":"Stable id echoed back on a CLICKED engagement"},"label":{"type":"string","description":"Button label shown on the notification"},"deep_link":{"type":"string","description":"Deep link opened by this button"}},"required":["action_id","label"]},"CreatePushCampaignDevApiDto":{"type":"object","properties":{"description":{"type":"string","example":"Ramadan flash sale, app users only"},"image_url":{"type":"string","description":"Large image (https only)."},"icon_url":{"type":"string","description":"Small icon (https only)."},"deep_link":{"type":"string","description":"Deep link opened on a body tap."},"android_channel_id":{"type":"string","description":"Android notification channel id."},"ttl_seconds":{"type":"number","description":"Time to live in seconds (60–2419200)."},"priority":{"type":"string","enum":["high","normal"]},"action_buttons":{"type":"array","items":{"$ref":"#/components/schemas/DevApiPushActionButtonDto"}},"data_payload":{"type":"object","additionalProperties":true,"description":"Flat string→string map delivered alongside the notification. `arsel_*`, `google*`, `gcm*` and FCM-reserved keys are rejected."},"target_platforms":{"type":"array","description":"Restrict the send to these platforms. Omit for all.","items":{"type":"string","enum":["android","ios","web"]}},"throttle_minutes":{"type":"number","description":"Throttle delivery, spreading the send over N minutes (max 720) so the whole audience does not open your backend at once."},"list_ids":{"items":{"type":"string","format":"uuid"},"description":"List IDs to target.","type":"array"},"tag_ids":{"items":{"type":"string","format":"uuid"},"description":"Tag IDs to target.","type":"array"},"segment_ids":{"items":{"type":"string","format":"uuid"},"description":"Segment IDs to target.","type":"array"},"smart_sending_enabled":{"type":"boolean","description":"Skip contacts messaged too recently on this channel. Defaults to true."},"name":{"type":"string","example":"Flash sale — 6h only"},"title":{"type":"string","description":"Notification title."},"body":{"type":"string","description":"Notification body."}},"required":["name","title","body"]},"PushCampaignIdResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Campaign ID","example":"0192a1b2-c3d4-7e5f-9000-abcdef123456"}},"required":["id"]},"PushCampaignResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"0192a1b2-c3d4-7e5f-9000-abcdef123456"},"name":{"type":"string","example":"Flash sale — 6h only"},"description":{"type":"string","nullable":true,"example":"Ramadan flash sale, app users only"},"title":{"type":"string","example":"Doors close at midnight"},"body":{"type":"string","example":"Everything 40% off for the next six hours."},"image_url":{"type":"string","nullable":true,"example":"https://cdn.example.com/sale.png"},"icon_url":{"type":"string","nullable":true,"example":"https://cdn.example.com/icon.png"},"deep_link":{"type":"string","nullable":true,"example":"myapp://sale"},"android_channel_id":{"type":"string","nullable":true,"example":"promotions"},"ttl_seconds":{"type":"number","example":86400},"priority":{"type":"string","example":"high"},"action_buttons":{"nullable":true,"type":"array","items":{"$ref":"#/components/schemas/DevApiPushActionButtonDto"}},"data_payload":{"type":"object","additionalProperties":true,"nullable":true,"example":{"order_id":"123"}},"target_platforms":{"type":"array","nullable":true,"items":{"type":"string","enum":["android","ios","web"]}},"throttle_minutes":{"type":"number","nullable":true,"example":15},"status":{"type":"string","enum":["draft","scheduled","queued","sending","paused","sent","failed","cancelled","archived"],"example":"draft"},"smart_sending_enabled":{"type":"boolean","example":true},"scheduled_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-09-01T09:00:00.000Z"},"sent_at":{"type":"string","nullable":true,"format":"date-time","example":"2026-09-01T09:05:00.000Z"},"created_at":{"type":"string","format":"date-time","example":"2026-08-10T12:00:00.000Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-08-10T12:00:00.000Z"}},"required":["id","name","description","title","body","image_url","icon_url","deep_link","android_channel_id","ttl_seconds","priority","action_buttons","data_payload","target_platforms","throttle_minutes","status","smart_sending_enabled","scheduled_at","sent_at","created_at","updated_at"]},"PushCampaignListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/PushCampaignResponseDto"}}},"required":["object","has_more","data"]},"UpdatePushCampaignDevApiDto":{"type":"object","properties":{"description":{"type":"string","example":"Ramadan flash sale, app users only"},"image_url":{"type":"string","description":"Large image (https only)."},"icon_url":{"type":"string","description":"Small icon (https only)."},"deep_link":{"type":"string","description":"Deep link opened on a body tap."},"android_channel_id":{"type":"string","description":"Android notification channel id."},"ttl_seconds":{"type":"number","description":"Time to live in seconds (60–2419200)."},"priority":{"type":"string","enum":["high","normal"]},"action_buttons":{"type":"array","items":{"$ref":"#/components/schemas/DevApiPushActionButtonDto"}},"data_payload":{"type":"object","additionalProperties":true,"description":"Flat string→string map delivered alongside the notification. `arsel_*`, `google*`, `gcm*` and FCM-reserved keys are rejected."},"target_platforms":{"type":"array","description":"Restrict the send to these platforms. Omit for all.","items":{"type":"string","enum":["android","ios","web"]}},"throttle_minutes":{"type":"number","description":"Throttle delivery, spreading the send over N minutes (max 720) so the whole audience does not open your backend at once."},"list_ids":{"items":{"type":"string","format":"uuid"},"description":"List IDs to target.","type":"array"},"tag_ids":{"items":{"type":"string","format":"uuid"},"description":"Tag IDs to target.","type":"array"},"segment_ids":{"items":{"type":"string","format":"uuid"},"description":"Segment IDs to target.","type":"array"},"smart_sending_enabled":{"type":"boolean","description":"Skip contacts messaged too recently on this channel. Defaults to true."},"name":{"type":"string","example":"Flash sale — 6h only"},"title":{"type":"string","description":"Notification title."},"body":{"type":"string","description":"Notification body."}}},"SendPushCampaignDevApiDto":{"type":"object","properties":{"scheduled_at":{"type":"string","format":"date-time","description":"ISO 8601 date-time to schedule the campaign for. Omit to send immediately. Must be in the future.","example":"2026-09-01T09:00:00Z"}}},"RegisterPushDeviceDevApiDto":{"type":"object","properties":{"contact_id":{"type":"string","format":"uuid","description":"Arsel contact UUID. Mutually exclusive with external_id, email and phone_number."},"external_id":{"type":"string","description":"Your own user id for this contact, the one the mobile and web SDKs identify with. The strongest identifier: tried before email and phone_number, and it survives email and phone changes."},"email":{"type":"string","description":"Contact email. Matched case-insensitively; authoritative when both email and phone_number are supplied."},"phone_number":{"type":"string","description":"Contact phone in E.164, e.g. +966501234567."},"installation_id":{"type":"string","description":"SDK-generated installation id. Stable across FCM token rotations — it, not the device token, is the natural key."},"platform":{"type":"string","enum":["android","ios","web"],"description":"Device family."},"apns_environment":{"type":"string","description":"Which APNs host this token is addressable on. iOS only. Send `sandbox` for debug and simulator builds — a token minted by one host is rejected by the other, and Apple gives no way to tell them apart from the token. Defaults to `production`, which is what App Store and TestFlight builds mint.","enum":["production","sandbox"]},"device_token":{"type":"string","description":"Push token for the device. An FCM registration token on android; the APNs device token on ios."},"endpoint":{"type":"string","description":"Web Push endpoint URL. Required for web."},"p256dh_key":{"type":"string","description":"Web Push P-256 ECDH public key."},"auth_key":{"type":"string","description":"Web Push auth secret."},"enablement_status":{"type":"string","enum":["authorized","denied","not_determined","provisional","unauthorized"],"description":"OS notification permission as your app last observed it. Registration is not consent — a device registers before the prompt and after a denial — so omit this rather than guessing. `provisional` is the iOS mode that delivers quietly to Notification Centre."},"sdk_version":{"type":"string","description":"Arsel SDK build running on the device, e.g. `android/1.2.0`. Supply it only if the SDK is embedded: it is what marks the device as instrumented for delivery/engagements."},"app_version":{"type":"string"},"os_version":{"type":"string"},"device_model":{"type":"string"},"device_manufacturer":{"type":"string"},"device_timezone":{"type":"string","description":"IANA zone, e.g. `Asia/Riyadh`. Unbackfillable — send it."},"device_locale":{"type":"string","description":"BCP-47, e.g. `ar-SA`."}},"required":["installation_id","platform"]},"RegisterPushDeviceResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"},"installation_id":{"type":"string","example":"install-abc123"},"status":{"type":"string","enum":["active","expired","revoked","failed"]},"contact_id":{"type":"string","format":"uuid","example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"},"opted_out":{"type":"boolean","example":false,"description":"True when a durable opt-out kept the device revoked despite this call. Registration never resurrects an opt-out."},"suppressed":{"type":"boolean","example":false,"description":"True when the device stayed retired because this call replayed a token the push service has already rejected as dead. Re-registering the same dead token never revives it — send the new token the device was issued."},"device_secret":{"type":"string","nullable":true,"description":"Per-device proof of possession, returned exactly once at creation and never again. Relay it to the app if it embeds the SDK; only its SHA-256 is stored."}},"required":["id","installation_id","status","contact_id","opted_out","suppressed"]},"ImportPushDeviceDevApiDto":{"type":"object","properties":{"contact_id":{"type":"string","format":"uuid","description":"Arsel contact UUID. Mutually exclusive with external_id, email and phone_number."},"external_id":{"type":"string","description":"Your own user id for this contact, the one the mobile and web SDKs identify with. The strongest identifier: tried before email and phone_number, and it survives email and phone changes."},"email":{"type":"string","description":"Contact email. Matched case-insensitively; authoritative when both email and phone_number are supplied."},"phone_number":{"type":"string","description":"Contact phone in E.164, e.g. +966501234567."},"installation_id":{"type":"string","description":"SDK-generated installation id. Stable across FCM token rotations — it, not the device token, is the natural key."},"platform":{"type":"string","enum":["android","ios","web"],"description":"Device family."},"apns_environment":{"type":"string","description":"Which APNs host this token is addressable on. iOS only. Send `sandbox` for debug and simulator builds — a token minted by one host is rejected by the other, and Apple gives no way to tell them apart from the token. Defaults to `production`, which is what App Store and TestFlight builds mint.","enum":["production","sandbox"]},"device_token":{"type":"string","description":"Push token for the device. An FCM registration token on android; the APNs device token on ios."},"endpoint":{"type":"string","description":"Web Push endpoint URL. Required for web."},"p256dh_key":{"type":"string","description":"Web Push P-256 ECDH public key."},"auth_key":{"type":"string","description":"Web Push auth secret."},"enablement_status":{"type":"string","enum":["authorized","denied","not_determined","provisional","unauthorized"],"description":"OS notification permission as your app last observed it. Registration is not consent — a device registers before the prompt and after a denial — so omit this rather than guessing. `provisional` is the iOS mode that delivers quietly to Notification Centre."},"sdk_version":{"type":"string","description":"Arsel SDK build running on the device, e.g. `android/1.2.0`. Supply it only if the SDK is embedded: it is what marks the device as instrumented for delivery/engagements."},"app_version":{"type":"string"},"os_version":{"type":"string"},"device_model":{"type":"string"},"device_manufacturer":{"type":"string"},"device_timezone":{"type":"string","description":"IANA zone, e.g. `Asia/Riyadh`. Unbackfillable — send it."},"device_locale":{"type":"string","description":"BCP-47, e.g. `ar-SA`."}},"required":["installation_id","platform"]},"BulkRegisterPushDevicesDevApiDto":{"type":"object","properties":{"devices":{"description":"Up to 1000 device registrations, each the single-device body.","type":"array","items":{"$ref":"#/components/schemas/ImportPushDeviceDevApiDto"}}},"required":["devices"]},"BulkRegisterPushDeviceItemErrorDto":{"type":"object","properties":{"index":{"type":"number","example":3,"description":"Position of the failed row in the `devices` array you submitted."},"status_code":{"type":"number","example":404},"code":{"type":"string","example":"contact_not_found"},"message":{"type":"string","example":"No contact in this organization matches the supplied reference."}},"required":["index","status_code","message"]},"BulkRegisterPushDevicesResponseDto":{"type":"object","properties":{"job_id":{"type":"string","format":"uuid","example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a","description":"Poll `GET /v1/push/imports/{id}` with this."},"accepted":{"type":"number","example":998,"description":"Rows queued for processing."},"rejected":{"type":"number","example":2,"description":"Rows rejected on shape and never queued."},"errors":{"description":"The rejected rows. Failures that only surface during processing are not here — read the import errors route once the job completes.","type":"array","items":{"$ref":"#/components/schemas/BulkRegisterPushDeviceItemErrorDto"}}},"required":["job_id","accepted","rejected","errors"]},"PushDeviceImportStatusResponseDto":{"type":"object","properties":{"job_id":{"type":"string","format":"uuid","example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"},"status":{"type":"string","enum":["queued","processing","completed","failed"],"description":"`failed` means the job died, not that its rows did — that is `completed` with a non-zero `failed` count."},"total":{"type":"number","example":998,"description":"Rows accepted for processing."},"completed":{"type":"number","example":995},"failed":{"type":"number","example":3},"errors":{"description":"One entry per failed row, each carrying the `index` it held in your submitted `devices` array. Bounded by the batch cap, so this is never truncated. Empty until the job completes.","type":"array","items":{"$ref":"#/components/schemas/BulkRegisterPushDeviceItemErrorDto"}},"error_message":{"type":"string","nullable":true,"description":"Set only when the job failed."},"created_at":{"type":"string","format":"date-time","example":"2026-06-01T12:00:00.000Z"},"completed_at":{"type":"string","format":"date-time","example":"2026-06-01T12:04:11.000Z","nullable":true}},"required":["job_id","status","total","completed","failed","errors","error_message","created_at","completed_at"]},"UnregisterPushDeviceDevApiDto":{"type":"object","properties":{"installation_id":{"type":"string","description":"Installation id to revoke."}},"required":["installation_id"]},"PushDeviceRevokedResponseDto":{"type":"object","properties":{"installation_id":{"type":"string","example":"install-abc123"},"status":{"type":"string","enum":["active","expired","revoked","failed"],"example":"revoked"}},"required":["installation_id","status"]},"PushDeviceResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"},"installation_id":{"type":"string","example":"install-abc123"},"platform":{"type":"string","enum":["android","ios","web"]},"vendor":{"type":"string","enum":["fcm","apns","web_push"]},"status":{"type":"string","enum":["active","expired","revoked","failed"]},"revoke_reason":{"type":"string","enum":["user_opt_out","contact_opt_out","dead_token","admin"],"nullable":true},"device_token_masked":{"type":"string","example":"fcm…J3x9","nullable":true,"description":"Masked device credential — the first and last few characters only. The raw token is a sendable credential and is never returned."},"enablement_status":{"type":"string","enum":["authorized","denied","not_determined","provisional","unauthorized"],"nullable":true},"sdk_enabled":{"type":"boolean","example":true,"description":"Whether the Arsel SDK is embedded on this device."},"sdk_version":{"type":"string","example":"android/1.2.0","nullable":true},"app_version":{"type":"string","example":"3.4.1","nullable":true},"device_model":{"type":"string","example":"Pixel 8","nullable":true},"device_timezone":{"type":"string","example":"Asia/Riyadh","nullable":true},"device_locale":{"type":"string","example":"ar-SA","nullable":true},"last_used_at":{"type":"string","format":"date-time","example":"2026-06-01T12:00:00.000Z","nullable":true},"last_seen_at":{"type":"string","format":"date-time","example":"2026-06-01T12:00:00.000Z","nullable":true},"created_at":{"type":"string","format":"date-time","example":"2026-06-01T12:00:00.000Z"}},"required":["id","installation_id","platform","vendor","status","revoke_reason","device_token_masked","enablement_status","sdk_enabled","sdk_version","app_version","device_model","device_timezone","device_locale","last_used_at","last_seen_at","created_at"]},"PushDeviceListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/PushDeviceResponseDto"}}},"required":["object","has_more","data"]},"SendPushDevApiDto":{"type":"object","properties":{"contact_id":{"type":"string","format":"uuid","description":"Arsel contact UUID. Mutually exclusive with external_id, email and phone_number."},"external_id":{"type":"string","description":"Your own user id for this contact, the one the mobile and web SDKs identify with. The strongest identifier: tried before email and phone_number, and it survives email and phone changes."},"email":{"type":"string","description":"Contact email. Matched case-insensitively; authoritative when both email and phone_number are supplied."},"phone_number":{"type":"string","description":"Contact phone in E.164, e.g. +966501234567."},"title":{"type":"string","description":"Notification title."},"body":{"type":"string","description":"Notification body."},"image_url":{"type":"string","description":"HTTPS image URL."},"icon_url":{"type":"string","description":"Small icon (https only)."},"deep_link":{"type":"string","description":"Deep link opened on tap."},"android_channel_id":{"type":"string","description":"Android notification channel id."},"category":{"type":"string","description":"Analytics category, e.g. `otp` or `order_update`."},"ttl_seconds":{"type":"number","description":"Time to live in seconds (60–2419200)."},"priority":{"type":"string","enum":["high","normal"]},"action_buttons":{"type":"array","items":{"$ref":"#/components/schemas/DevApiPushActionButtonDto"}},"data_payload":{"type":"object","additionalProperties":true,"description":"Flat string→string map delivered alongside the notification. `arsel_*`, `google*`, `gcm*` and FCM-reserved keys are rejected."}},"required":["title","body"]},"PushDetailResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"01957e3a-4b5c-7d8e-9f0a-1b2c3d4e5f6a"},"title":{"type":"string","nullable":true,"description":"Notification title. null once the content has been purged.","example":"Your order shipped"},"body":{"type":"string","nullable":true,"description":"Notification body. null once the content has been purged.","example":"Order A-1023 is on its way."},"category":{"type":"string","nullable":true,"example":"order_update"},"devices_targeted":{"type":"number","description":"Devices the contact had registered when the push was sent.","example":2},"devices_accepted":{"type":"number","description":"Devices the push provider (FCM, APNs or web push) accepted the notification for. Accepted is not delivered: providers do not confirm arrival on the device.","example":2},"created_at":{"format":"date-time","type":"string","example":"2026-09-27T12:00:00.000Z"}},"required":["id","title","body","category","devices_targeted","devices_accepted","created_at"]},"PushListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/PushDetailResponseDto"}}},"required":["object","has_more","data"]},"SendWhatsappDto":{"type":"object","properties":{"to":{"type":"string","description":"Recipient in E.164 format, including the country code.","example":"+966512345678"},"template_name":{"type":"string","description":"Name of an APPROVED template in your account. Templates are created and approved before they can be sent.","example":"order_shipped"},"language":{"type":"string","description":"Template language code. Omit when your account has exactly one approved template with this name.","example":"en"},"parameters":{"type":"object","additionalProperties":true,"description":"Values for the template placeholders, keyed by placeholder name. For an authentication template, pass the one-time code as `code`.","example":{"firstName":"Sara","orderId":"A-1029"}}},"required":["to","template_name"]},"WhatsappDetailResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"to":{"type":"string","example":"+966512345678"},"template_name":{"type":"string"},"language":{"type":"string"},"category":{"type":"string","enum":["marketing","utility","authentication"]},"status":{"type":"string","enum":["queued","sent","delivered","read","failed"]},"failure_reason":{"type":"string","nullable":true,"description":"Why the send failed. Present only when status is failed."},"sent_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"format":"date-time","type":"string"}},"required":["id","to","template_name","language","category","status","failure_reason","sent_at","created_at"]},"WhatsappListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/WhatsappDetailResponseDto"}}},"required":["object","has_more","data"]},"CreateSegmentDevApiDto":{"type":"object","properties":{"name":{"type":"string","example":"VIP customers","maxLength":100},"description":{"type":"string","example":"Opened 3+ emails in the last 90 days","maxLength":500},"filter":{"allOf":[{"$ref":"#/components/schemas/ContactFilter"}],"description":"The rules a contact must match to be in the segment.","example":{"type":"group","operator":"and","conditions":[{"type":"engagement_count","channel":"email","action":"opened","operator":"greater_than_or_equal","value":3,"time_window":{"value":90,"unit":"days"}},{"type":"channel_status","channel":"email","consent":"subscribed"}]}}},"required":["name","filter"]},"SegmentResponseDto":{"type":"object","properties":{"id":{"type":"string","format":"uuid","example":"0192a1b2-c3d4-7e5f-9000-abcdef123456"},"name":{"type":"string","example":"VIP customers"},"description":{"type":"string","nullable":true,"example":"Spent over 1,000 SAR in the last 90 days"},"status":{"type":"string","enum":["processing","ready"],"example":"ready","description":"`processing` until the members have been computed for the current rules, then `ready`."},"filter":{"allOf":[{"$ref":"#/components/schemas/ContactFilter"}],"description":"The rules a contact must match to be in the segment.","example":{"type":"group","operator":"and","conditions":[{"type":"engagement_count","channel":"email","action":"opened","operator":"greater_than_or_equal","value":3,"time_window":{"value":90,"unit":"days"}},{"type":"channel_status","channel":"email","consent":"subscribed"}]}},"contact_count":{"type":"number","nullable":true,"example":420,"description":"Contacts matching the segment when it was last counted. `null` while `status` is `processing`."},"counted_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-03-08T12:00:00.000Z","description":"When `contact_count` was computed."},"created_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"},"updated_at":{"type":"string","format":"date-time","example":"2026-03-08T12:00:00.000Z"}},"required":["id","name","description","status","filter","contact_count","counted_at","created_at","updated_at"]},"SegmentListResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"list","enum":["list"]},"has_more":{"type":"boolean","example":false},"data":{"type":"array","items":{"$ref":"#/components/schemas/SegmentResponseDto"}}},"required":["object","has_more","data"]},"UpdateSegmentDevApiDto":{"type":"object","properties":{"name":{"type":"string","example":"VIP customers","maxLength":100},"description":{"type":"string","example":"Opened 3+ emails in the last 90 days","maxLength":500},"filter":{"allOf":[{"$ref":"#/components/schemas/ContactFilter"}],"description":"Replaces the whole rule tree. The members are recomputed, and `status` is `processing` until they are.","example":{"type":"group","operator":"and","conditions":[{"type":"engagement_count","channel":"email","action":"opened","operator":"greater_than_or_equal","value":3,"time_window":{"value":90,"unit":"days"}},{"type":"channel_status","channel":"email","consent":"subscribed"}]}}}},"UsageMeterDto":{"type":"object","properties":{"used":{"type":"number","example":18240},"limit":{"type":"number","nullable":true,"example":50000,"description":"The plan's limit. `null` means the plan sets no limit."},"remaining":{"type":"number","nullable":true,"example":31760,"description":"What is left of `limit`. `null` when there is no limit."}},"required":["used","limit","remaining"]},"EmailUsageDto":{"type":"object","properties":{"marketing":{"$ref":"#/components/schemas/UsageMeterDto"},"transactional":{"$ref":"#/components/schemas/UsageMeterDto"}},"required":["marketing","transactional"]},"UsageResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"usage","enum":["usage"]},"period_start":{"type":"string","format":"date-time","example":"2026-03-01T00:00:00.000Z"},"period_end":{"type":"string","format":"date-time","example":"2026-04-01T00:00:00.000Z","description":"Exclusive: the instant the next period starts."},"emails":{"$ref":"#/components/schemas/EmailUsageDto"},"sms":{"description":"SMS credits. One credit is one message part, so a long message uses several.","allOf":[{"$ref":"#/components/schemas/UsageMeterDto"}]},"whatsapp":{"description":"WhatsApp messages.","allOf":[{"$ref":"#/components/schemas/UsageMeterDto"}]},"push":{"description":"Push notifications.","allOf":[{"$ref":"#/components/schemas/UsageMeterDto"}]},"in_app":{"description":"In-app messages.","allOf":[{"$ref":"#/components/schemas/UsageMeterDto"}]},"automation_runs":{"$ref":"#/components/schemas/UsageMeterDto"},"contacts":{"description":"Contacts in the organization right now, against the plan limit. Not a per-period counter.","allOf":[{"$ref":"#/components/schemas/UsageMeterDto"}]}},"required":["object","period_start","period_end","emails","sms","whatsapp","push","in_app","automation_runs","contacts"]},"SmartSendingStatsDto":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether Smart Sending was on when the campaign was sent."},"skipped":{"type":"number","example":140,"description":"Recipients skipped because they had already reached the sending cap."}},"required":["enabled","skipped"]},"EmailLinkStatsDto":{"type":"object","properties":{"url":{"type":"string","example":"https://example.com/sale"},"clicks":{"type":"number","example":311}},"required":["url","clicks"]},"CurrencyAmountStatsDto":{"type":"object","properties":{"currency":{"type":"string","example":"SAR"},"amount":{"type":"number","example":18350}},"required":["currency","amount"]},"ConversionEventStatsDto":{"type":"object","properties":{"event_id":{"type":"string","format":"uuid"},"event_name":{"type":"string","example":"order_completed"},"tracks_revenue":{"type":"boolean","description":"Whether this event maps a revenue value."},"conversions":{"type":"number","example":42},"revenue":{"type":"array","items":{"$ref":"#/components/schemas/CurrencyAmountStatsDto"}}},"required":["event_id","event_name","tracks_revenue","conversions","revenue"]},"ConversionStatsDto":{"type":"object","properties":{"enabled":{"type":"boolean","description":"False when the organization has no conversion events; the other fields are then empty."},"tracking_since":{"type":"string","format":"date-time","nullable":true,"description":"When the earliest conversion event was enabled."},"total":{"type":"number","example":42,"description":"Conversions attributed to this campaign."},"revenue":{"description":"Attributed revenue, one entry per currency.","type":"array","items":{"$ref":"#/components/schemas/CurrencyAmountStatsDto"}},"by_event":{"type":"array","items":{"$ref":"#/components/schemas/ConversionEventStatsDto"}}},"required":["enabled","tracking_since","total","revenue","by_event"]},"EmailCampaignStatsResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"email_campaign_stats","enum":["email_campaign_stats"]},"campaign_id":{"type":"string","format":"uuid"},"sent":{"type":"number","example":12040},"delivered":{"type":"number","example":11800},"failed":{"type":"number","example":12,"description":"Messages the mail provider never accepted."},"bounced":{"type":"number","example":228,"description":"All bounces: `hard_bounced` + `soft_bounced` + `validator_rejected`."},"hard_bounced":{"type":"number","example":90},"soft_bounced":{"type":"number","example":120},"validator_rejected":{"type":"number","example":18,"description":"Addresses the email validator rejected before sending. Counted in `bounced`."},"opens":{"type":"number","example":6100,"description":"Every open, repeats included."},"unique_opens":{"type":"number","example":4720,"description":"Contacts who opened."},"clicks":{"type":"number","example":980,"description":"Every click, repeats included."},"unique_clicks":{"type":"number","example":610,"description":"Contacts who clicked."},"unsubscribed":{"type":"number","example":31},"complained":{"type":"number","example":2,"description":"Spam complaints."},"delivery_rate":{"type":"number","nullable":true,"example":0.9801,"description":"`delivered` / `sent`. A fraction from 0 to 1. `null` when the denominator is 0."},"open_rate":{"type":"number","nullable":true,"example":0.4,"description":"`unique_opens` / `delivered`. A fraction from 0 to 1. `null` when the denominator is 0."},"click_rate":{"type":"number","nullable":true,"example":0.0517,"description":"`unique_clicks` / `delivered`. A fraction from 0 to 1. `null` when the denominator is 0."},"bounce_rate":{"type":"number","nullable":true,"example":0.0189,"description":"`bounced` / `sent`. A fraction from 0 to 1. `null` when the denominator is 0."},"unsubscribe_rate":{"type":"number","nullable":true,"example":0.0026,"description":"`unsubscribed` / `delivered`. A fraction from 0 to 1. `null` when the denominator is 0."},"complaint_rate":{"type":"number","nullable":true,"example":0.0002,"description":"`complained` / `delivered`. A fraction from 0 to 1. `null` when the denominator is 0."},"smart_sending":{"$ref":"#/components/schemas/SmartSendingStatsDto"},"links":{"description":"Clicks per link, most clicked first. Unsubscribe links are left out.","type":"array","items":{"$ref":"#/components/schemas/EmailLinkStatsDto"}},"conversions":{"$ref":"#/components/schemas/ConversionStatsDto"}},"required":["object","campaign_id","sent","delivered","failed","bounced","hard_bounced","soft_bounced","validator_rejected","opens","unique_opens","clicks","unique_clicks","unsubscribed","complained","delivery_rate","open_rate","click_rate","bounce_rate","unsubscribe_rate","complaint_rate","smart_sending","links","conversions"]},"SmsCampaignStatsResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"sms_campaign_stats","enum":["sms_campaign_stats"]},"campaign_id":{"type":"string","format":"uuid"},"recipients":{"type":"number","example":5200,"description":"Contacts the campaign was sent to. Before it is sent, the current audience size."},"sent":{"type":"number","example":5180},"delivered":{"type":"number","example":4960},"undelivered":{"type":"number","example":110},"failed":{"type":"number","example":4,"description":"Messages the SMS provider never accepted."},"blocked":{"type":"number","example":6},"rejected":{"type":"number","example":3},"expired":{"type":"number","example":21},"unknown":{"type":"number","example":9,"description":"Delivery receipts with a status the provider did not name."},"pending":{"type":"number","example":71,"description":"Accepted by the provider with no final receipt yet."},"clicks":{"type":"number","example":402,"description":"Every click, repeats included. Link-scanner clicks excluded."},"unique_clicks":{"type":"number","example":371,"description":"Contacts who clicked."},"bot_clicks":{"type":"number","example":58,"description":"Clicks identified as link scanners. Not counted in `clicks`."},"delivery_rate":{"type":"number","nullable":true,"example":0.9575,"description":"`delivered` / `sent`. A fraction from 0 to 1. `null` when the denominator is 0."},"click_rate":{"type":"number","nullable":true,"example":0.0748,"description":"`unique_clicks` / `delivered`. A fraction from 0 to 1. `null` when the denominator is 0."},"smart_sending":{"$ref":"#/components/schemas/SmartSendingStatsDto"},"conversions":{"$ref":"#/components/schemas/ConversionStatsDto"}},"required":["object","campaign_id","recipients","sent","delivered","undelivered","failed","blocked","rejected","expired","unknown","pending","clicks","unique_clicks","bot_clicks","delivery_rate","click_rate","smart_sending","conversions"]},"PushCampaignStatsResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"push_campaign_stats","enum":["push_campaign_stats"]},"campaign_id":{"type":"string","format":"uuid"},"recipients":{"type":"number","example":8400,"description":"Contacts the campaign was sent to. Before it is sent, the contacts it can currently reach."},"devices":{"type":"number","example":9150,"description":"Devices behind `recipients`."},"attempts":{"type":"number","example":9150,"description":"`delivered` + `failures` + `token_invalidations`."},"delivered":{"type":"number","example":8890,"description":"Notifications accepted by APNs, FCM or Web Push. Acceptance, not arrival on the device."},"confirmed":{"type":"number","example":6120,"description":"Notifications the device reported receiving. A floor: not every device reports."},"displayed":{"type":"number","example":5980},"suppressed":{"type":"number","example":140,"description":"Notifications the device received but did not show."},"opens":{"type":"number","example":1310,"description":"Every open, repeats included."},"clicks":{"type":"number","example":420,"description":"Every click, repeats included."},"dismissals":{"type":"number","example":260},"failures":{"type":"number","example":75},"token_invalidations":{"type":"number","example":185,"description":"Sends refused because the device token was no longer valid."},"bot_opens":{"type":"number","example":12,"description":"Opens whose signature failed verification. Not counted in `opens`."},"bot_clicks":{"type":"number","example":3,"description":"Clicks whose signature failed verification. Not counted in `clicks`."},"unique_delivered":{"type":"number","example":8200,"description":"Contacts with at least one delivered notification."},"unique_displayed":{"type":"number","example":5600},"unique_opens":{"type":"number","example":1180,"description":"Contacts who opened."},"unique_clicks":{"type":"number","example":390,"description":"Contacts who clicked."},"delivery_rate":{"type":"number","nullable":true,"example":0.9716,"description":"`delivered` / `attempts`. A fraction from 0 to 1. `null` when the denominator is 0."},"open_rate":{"type":"number","nullable":true,"example":0.1439,"description":"`unique_opens` / `unique_delivered`. A fraction from 0 to 1. `null` when the denominator is 0."},"click_rate":{"type":"number","nullable":true,"example":0.0476,"description":"`unique_clicks` / `unique_delivered`. A fraction from 0 to 1. `null` when the denominator is 0."},"smart_sending":{"$ref":"#/components/schemas/SmartSendingStatsDto"},"conversions":{"$ref":"#/components/schemas/ConversionStatsDto"}},"required":["object","campaign_id","recipients","devices","attempts","delivered","confirmed","displayed","suppressed","opens","clicks","dismissals","failures","token_invalidations","bot_opens","bot_clicks","unique_delivered","unique_displayed","unique_opens","unique_clicks","delivery_rate","open_rate","click_rate","smart_sending","conversions"]},"InAppCampaignStatsResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"in_app_campaign_stats","enum":["in_app_campaign_stats"]},"campaign_id":{"type":"string","format":"uuid"},"impressions":{"type":"number","example":15200},"clicks":{"type":"number","example":1840},"dismissals":{"type":"number","example":9300},"expirations":{"type":"number","example":410,"description":"Messages that expired on the device without being shown."},"bot_impressions":{"type":"number","example":22,"description":"Impressions identified as automated. Not counted in `impressions`."},"bot_clicks":{"type":"number","example":4,"description":"Clicks identified as automated. Not counted in `clicks`."},"devices_reached":{"type":"number","example":11750,"description":"Devices that have shown the message at least once."},"click_rate":{"type":"number","nullable":true,"example":0.1211,"description":"`clicks` / `impressions`. A fraction from 0 to 1. `null` when the denominator is 0."}},"required":["object","campaign_id","impressions","clicks","dismissals","expirations","bot_impressions","bot_clicks","devices_reached","click_rate"]},"WhatsappCampaignStatsResponseDto":{"type":"object","properties":{"object":{"type":"string","example":"whatsapp_campaign_stats","enum":["whatsapp_campaign_stats"]},"campaign_id":{"type":"string","format":"uuid"},"sent":{"type":"number","example":3100},"delivered":{"type":"number","example":3020},"read":{"type":"number","example":2410,"description":"Read receipts received."},"failed":{"type":"number","example":80},"clicks":{"type":"number","example":560,"description":"Button taps, repeats included."},"unique_reads":{"type":"number","example":2290,"description":"Contacts who read."},"unique_clicks":{"type":"number","example":498,"description":"Contacts who tapped a button."},"delivery_rate":{"type":"number","nullable":true,"example":0.9742,"description":"`delivered` / `sent`. A fraction from 0 to 1. `null` when the denominator is 0."},"read_rate":{"type":"number","nullable":true,"example":0.7583,"description":"`unique_reads` / `delivered`. A fraction from 0 to 1. `null` when the denominator is 0."},"click_rate":{"type":"number","nullable":true,"example":0.1649,"description":"`unique_clicks` / `delivered`. A fraction from 0 to 1. `null` when the denominator is 0."},"conversions":{"$ref":"#/components/schemas/ConversionStatsDto"}},"required":["object","campaign_id","sent","delivered","read","failed","clicks","unique_reads","unique_clicks","delivery_rate","read_rate","click_rate","conversions"]},"ContactFilter":{"type":"object","properties":{"type":{"type":"string","enum":["group"]},"operator":{"type":"string","enum":["and","or"]},"conditions":{"type":"array","items":{"oneOf":[{"$ref":"#/components/schemas/ContactFilter"},{"type":"object","properties":{"type":{"type":"string","enum":["attribute"]},"field":{"type":"string"},"operator":{"type":"string","enum":["equals","not_equals","contains","not_contains","starts_with","ends_with","exists","does_not_exist","greater_than","greater_than_or_equal","less_than","less_than_or_equal","date_is_same_month_day"]},"value":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]}},"required":["type","field","operator"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["list_membership"]},"list_ids":{"minItems":1,"type":"array","items":{"type":"string","minLength":1}},"negate":{"type":"boolean"}},"required":["type","list_ids"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["tag"]},"tag_ids":{"minItems":1,"type":"array","items":{"type":"string","minLength":1}},"negate":{"type":"boolean"}},"required":["type","tag_ids"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["engagement_count"]},"channel":{"type":"string","enum":["email","sms","push"]},"action":{"type":"string","enum":["sent","delivered","opened","clicked"]},"operator":{"type":"string","enum":["equals","not_equals","greater_than","greater_than_or_equal","less_than","less_than_or_equal"]},"value":{"type":"integer","minimum":0,"maximum":9007199254740991},"time_window":{"type":"object","properties":{"value":{"type":"integer","minimum":1,"maximum":365},"unit":{"type":"string","enum":["days","weeks","months"]}},"required":["value","unit"],"additionalProperties":false}},"required":["type","channel","action","operator","value"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["last_engagement"]},"channel":{"type":"string","enum":["email","sms","whatsapp","push","in_app"]},"action":{"type":"string","enum":["sent","delivered","opened","clicked"]},"operator":{"type":"string","enum":["equals","not_equals","greater_than","greater_than_or_equal","less_than","less_than_or_equal","exists","does_not_exist"]},"value":{"type":"string"}},"required":["type","channel","action","operator"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["message_engagement"]},"target_type":{"type":"string","enum":["campaign","automation","automation_step","message"]},"target_id":{"type":"string"},"channel":{"type":"string","enum":["email","sms","whatsapp","push","in_app"]},"action":{"type":"string","enum":["sent","delivered","opened","clicked"]},"negate":{"type":"boolean"}},"required":["type","target_type","target_id","action"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["event"]},"event_name":{"type":"string","maxLength":255},"count":{"type":"object","properties":{"operator":{"type":"string","enum":["at_least","at_most","exactly"]},"value":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["operator","value"],"additionalProperties":false},"time_window":{"type":"object","properties":{"value":{"type":"integer","minimum":1,"maximum":365},"unit":{"type":"string","enum":["days","weeks","months"]}},"required":["value","unit"],"additionalProperties":false},"where":{"maxItems":10,"type":"array","items":{"type":"object","properties":{"field":{"type":"string","pattern":"^[A-Za-z0-9_.-]{1,80}$"},"operator":{"type":"string","enum":["equals","not_equals","contains","not_contains","starts_with","ends_with","exists","does_not_exist","greater_than","greater_than_or_equal","less_than","less_than_or_equal"]},"value":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]},"value_type":{"type":"string","enum":["string","number","boolean","date"]}},"required":["field","operator","value_type"],"additionalProperties":false}},"negate":{"type":"boolean"}},"required":["type","event_name"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["channel_status"]},"channel":{"type":"string","enum":["email","sms","whatsapp","push"]},"consent":{"type":"string","enum":["subscribed","unsubscribed"]},"deliverability":{"type":"string","enum":["deliverable","undeliverable"]}},"required":["type","channel"],"additionalProperties":false}]}}},"required":["type","operator","conditions"],"additionalProperties":false,"description":"A group of rules a contact must match. Groups nest up to 10 levels and hold at most 100 rules in total."}}}}