{"openapi":"3.0.0","paths":{"/metrics":{"get":{"operationId":"PrometheusController_index","parameters":[],"responses":{"200":{"description":""}},"tags":["Prometheus"]}},"/live":{"get":{"operationId":"HealthController_live","parameters":[],"responses":{"200":{"description":""}},"summary":"Liveness probe — process is alive","tags":["Health"]}},"/ready":{"get":{"operationId":"HealthController_ready","parameters":[],"responses":{"200":{"description":"The Health Check is successful","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"info":{"type":"object","example":{"database":{"status":"up"}},"additionalProperties":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}},"additionalProperties":true},"nullable":true},"error":{"type":"object","example":{},"additionalProperties":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}},"additionalProperties":true},"nullable":true},"details":{"type":"object","example":{"database":{"status":"up"}},"additionalProperties":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}},"additionalProperties":true}}}}}}},"503":{"description":"The Health Check is not successful","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"info":{"type":"object","example":{"database":{"status":"up"}},"additionalProperties":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}},"additionalProperties":true},"nullable":true},"error":{"type":"object","example":{"redis":{"status":"down","message":"Could not connect"}},"additionalProperties":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}},"additionalProperties":true},"nullable":true},"details":{"type":"object","example":{"database":{"status":"up"},"redis":{"status":"down","message":"Could not connect"}},"additionalProperties":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}},"additionalProperties":true}}}}}}}},"summary":"Readiness probe — can serve traffic","tags":["Health"]}},"/health":{"get":{"operationId":"HealthController_fullHealth","parameters":[],"responses":{"200":{"description":"The Health Check is successful","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"info":{"type":"object","example":{"database":{"status":"up"}},"additionalProperties":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}},"additionalProperties":true},"nullable":true},"error":{"type":"object","example":{},"additionalProperties":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}},"additionalProperties":true},"nullable":true},"details":{"type":"object","example":{"database":{"status":"up"}},"additionalProperties":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}},"additionalProperties":true}}}}}}},"503":{"description":"The Health Check is not successful","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"error"},"info":{"type":"object","example":{"database":{"status":"up"}},"additionalProperties":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}},"additionalProperties":true},"nullable":true},"error":{"type":"object","example":{"redis":{"status":"down","message":"Could not connect"}},"additionalProperties":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}},"additionalProperties":true},"nullable":true},"details":{"type":"object","example":{"database":{"status":"up"},"redis":{"status":"down","message":"Could not connect"}},"additionalProperties":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}},"additionalProperties":true}}}}}}}},"summary":"Full health report — all dependencies","tags":["Health"]}},"/v1/auth/signup":{"post":{"operationId":"AuthController_signup","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupDto"}}}},"responses":{"201":{"description":""}},"summary":"Create a new user + initial workspace","tags":["Auth"]}},"/v1/auth/login":{"post":{"operationId":"AuthController_login","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginDto"}}}},"responses":{"200":{"description":""}},"summary":"Authenticate with email + password. If the user has 2FA enabled, returns { mfaRequired: true, mfaChallenge: '<token>' } and NO session cookie — call /auth/login/mfa with the 6-digit code (or recovery code) to complete sign-in.","tags":["Auth"]}},"/v1/auth/login/mfa":{"post":{"operationId":"AuthController_completeMfaLogin","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MfaLoginDto"}}}},"responses":{"200":{"description":""}},"summary":"Complete a 2FA-gated login. Pass the mfaChallenge token returned by /auth/login and the user's current 6-digit TOTP code (or an xxxx-xxxx recovery code). On success, returns the standard auth payload + sets the session cookie.","tags":["Auth"]}},"/v1/auth/logout":{"post":{"operationId":"AuthController_logout","parameters":[],"responses":{"204":{"description":""}},"security":[{"JWT":[]},{"cookie":[]}],"summary":"Invalidate the current session","tags":["Auth"]}},"/v1/auth/me":{"get":{"operationId":"AuthController_me","parameters":[],"responses":{"200":{"description":""}},"security":[{"JWT":[]},{"cookie":[]}],"summary":"Return the authenticated user + accessible workspaces","tags":["Auth"]}},"/v1/me/mfa/status":{"get":{"operationId":"MfaController_status","parameters":[],"responses":{"200":{"description":""}},"security":[{"cookie":[]}],"summary":"Whether 2FA is enabled + unused-recovery-code count","tags":["MFA"]}},"/v1/me/mfa/enroll":{"post":{"operationId":"MfaController_enroll","parameters":[],"responses":{"200":{"description":""}},"security":[{"cookie":[]}],"summary":"Start 2FA enrollment. Returns the otpauth:// URI (render as QR) + the base32 secret (for manual entry). Call /me/mfa/confirm with a code from your authenticator app to activate.","tags":["MFA"]}},"/v1/me/mfa/confirm":{"post":{"operationId":"MfaController_confirm","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmEnrollDto"}}}},"responses":{"200":{"description":""}},"security":[{"cookie":[]}],"summary":"Confirm 2FA enrollment by submitting the current 6-digit code from your authenticator. Returns the 10 one-time recovery codes — shown ONCE.","tags":["MFA"]}},"/v1/me/mfa/disable":{"post":{"operationId":"MfaController_disable","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisableDto"}}}},"responses":{"200":{"description":""}},"security":[{"cookie":[]}],"summary":"Disable 2FA. Requires the current password — a stolen session alone shouldn't be enough to remove the second factor.","tags":["MFA"]}},"/v1/me/mfa/recovery-codes/regenerate":{"post":{"operationId":"MfaController_regenerateRecoveryCodes","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisableDto"}}}},"responses":{"200":{"description":""}},"security":[{"cookie":[]}],"summary":"Burn all existing recovery codes and issue a fresh batch. Shown ONCE. Requires the current password.","tags":["MFA"]}},"/v1/auth/signup/config":{"get":{"operationId":"SignupConfigController_config","parameters":[],"responses":{"200":{"description":""}},"summary":"Public signup config — Turnstile site key, AUP URL, industry dropdown options.","tags":["Auth"]}},"/v1/auth/google/start":{"get":{"operationId":"GoogleAuthController_start","parameters":[],"responses":{"200":{"description":""}},"summary":"Begin Google OAuth — 302s to Google's consent screen.","tags":["Auth"]}},"/v1/auth/google/callback":{"get":{"operationId":"GoogleAuthController_callback","parameters":[],"responses":{"200":{"description":""}},"summary":"Google OAuth callback — exchanges code, logs in or routes to finish-signup.","tags":["Auth"]}},"/v1/auth/google/signup/finish":{"post":{"operationId":"GoogleAuthController_finishSignup","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FinishSignupDto"}}}},"responses":{"201":{"description":""}},"summary":"Complete signup after Google OAuth. Pass the token returned in the redirect query + the workspace fields.","tags":["Auth"]}},"/v1/auth/signup/phone/start":{"post":{"operationId":"PhoneVerifyController_start","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartPhoneVerifyDto"}}}},"responses":{"200":{"description":""}},"summary":"Send a 6-digit SMS verification code to a phone number. Strictly rate-limited (2/min/IP, 10/hour/IP) — SMS is paid traffic. Per-phone 60s cooldown. Returns the verificationId to include in /verify.","tags":["Auth"]}},"/v1/auth/signup/phone/verify":{"post":{"operationId":"PhoneVerifyController_verifyCode","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyPhoneDto"}}}},"responses":{"200":{"description":""}},"summary":"Validate the 6-digit code against the stored hash. On success the verification row is marked consumed — pass the same verificationId to /auth/signup within ~30 min to claim the verified phone. The endpoint is idempotent: a second verify with the same code returns OTP_ALREADY_USED rather than re-consuming.","tags":["Auth"]}},"/v1/notifications":{"get":{"operationId":"WorkspaceNotificationsController_list","parameters":[{"name":"unreadOnly","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List this workspace's notifications (blocks, pacing notices, suspensions) with the unread count. Pass ?unreadOnly=true to filter.","tags":["Notifications"]}},"/v1/notifications/read":{"patch":{"operationId":"WorkspaceNotificationsController_markRead","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkReadDto"}}}},"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"Mark notifications read (specific ids, or all unread when omitted).","tags":["Notifications"]}},"/v1/api-keys":{"post":{"operationId":"ApiKeysController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyDto"}}}},"responses":{"201":{"description":""}},"security":[{"cookie":[]}],"summary":"Create a new API key — plaintext shown ONCE in the response","tags":["API Keys"]},"get":{"operationId":"ApiKeysController_list","parameters":[],"responses":{"200":{"description":""}},"security":[{"cookie":[]}],"summary":"List API keys (without plaintext) for current workspace","tags":["API Keys"]}},"/v1/api-keys/{id}":{"delete":{"operationId":"ApiKeysController_revoke","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"security":[{"cookie":[]}],"summary":"Revoke an API key","tags":["API Keys"]}},"/v1/billing/subscription":{"get":{"operationId":"BillingController_getSubscription","parameters":[],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Get the workspace's current subscription (plan, status, period).","tags":["Billing"]}},"/v1/billing/invoices":{"get":{"operationId":"BillingController_listInvoices","parameters":[],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"List recent invoices for the workspace.","tags":["Billing"]}},"/v1/billing/checkout":{"post":{"operationId":"BillingController_startCheckout","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartCheckoutDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Start a Stripe Checkout session for an upgrade. Returns { url } to redirect the customer to.","tags":["Billing"]}},"/v1/billing/portal":{"post":{"operationId":"BillingController_createPortal","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePortalDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Create a Stripe Billing Portal session — customer self-service for cancellation, payment method, invoices.","tags":["Billing"]}},"/v1/billing/webhook":{"post":{"operationId":"BillingController_stripeWebhook","parameters":[{"name":"stripe-signature","required":true,"in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Stripe webhook receiver. Signature-verified; not customer-facing.","tags":["Billing"]}},"/v1/billing/plans":{"get":{"operationId":"PaymentsController_plans","parameters":[],"responses":{"200":{"description":""}},"summary":"Self-serve plans, USD prices, and enabled payment methods.","tags":["Payments"]}},"/v1/billing/checkout/quote":{"get":{"operationId":"PaymentsController_quote","parameters":[{"name":"plan","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Preview the TRY amount and VAT for a plan before checkout.","tags":["Payments"]}},"/v1/billing/checkout/provider":{"post":{"operationId":"PaymentsController_checkout","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Start a prepaid checkout via iyzico (card). Returns { orderId, hostedUrl } — redirect the customer to hostedUrl.","tags":["Payments"]}},"/v1/billing/iyzico/callback":{"post":{"operationId":"PaymentsController_iyzicoCallback","parameters":[],"responses":{"200":{"description":""}},"summary":"iyzico Checkout Form callback (re-verified against the iyzico API).","tags":["Payments"]}},"/v1/billing/mamo/webhook":{"post":{"operationId":"PaymentsController_mamoWebhook","parameters":[],"responses":{"200":{"description":""}},"summary":"Mamo Pay payment webhook (re-verified against the Mamo API).","tags":["Payments"]}},"/v1/billing/gowallet/webhook":{"post":{"operationId":"PaymentsController_gowalletWebhook","parameters":[],"responses":{"200":{"description":""}},"summary":"GoWallet crypto webhook (re-verified against the GoWallet API).","tags":["Payments"]}},"/v1/campaigns":{"post":{"operationId":"CampaignsController_create","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCampaignDto"}}}},"responses":{"201":{"description":""}},"security":[{"JWT":[]}],"summary":"Create a draft campaign.","tags":["Campaigns"]},"get":{"operationId":"CampaignsController_list","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"cursor","required":true,"in":"query","schema":{"type":"string"}},{"name":"status","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"List campaigns (cursor-paginated). Optional ?status=.","tags":["Campaigns"]}},"/v1/campaigns/{idOrSlug}":{"get":{"operationId":"CampaignsController_get","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Get a campaign by id or slug.","tags":["Campaigns"]},"patch":{"operationId":"CampaignsController_update","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCampaignDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Update a campaign's editable fields. Only DRAFT and SCHEDULED campaigns are editable.","tags":["Campaigns"]},"delete":{"operationId":"CampaignsController_remove","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Soft-delete a campaign. Cancel first if it is currently SENDING.","tags":["Campaigns"]}},"/v1/campaigns/{idOrSlug}/stats":{"get":{"operationId":"CampaignsController_stats","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Get the denormalised counters for this campaign.","tags":["Campaigns"]}},"/v1/campaigns/{idOrSlug}/compliance-check":{"get":{"operationId":"CampaignsController_complianceCheck","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Pre-send compliance scan: verifies the From-domain is workspace-VERIFIED, the body contains an unsubscribe link (or {{ unsubscribe_url }} merge tag), and the workspace has a postal address set + included in the body. Returns { ok, findings } with severity=block | warn. The same check runs at send time and aborts the send if ok=false. Use this from the dashboard to show fixable issues in the review step BEFORE the customer hits Send.","tags":["Campaigns"]}},"/v1/campaigns/{idOrSlug}/links":{"get":{"operationId":"CampaignsController_links","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Per-link click breakdown — every unique URL referenced in the campaign with totalClicks (every click event) and uniqueClicks (recipients who clicked at least once), plus first/last click time. Ordered by totalClicks desc. Capped at 200 links.","tags":["Campaigns"]}},"/v1/campaigns/{idOrSlug}/timeline":{"get":{"operationId":"CampaignsController_timeline","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Hourly engagement timeline — opens + clicks bucketed by hour for the 7 days following the first send. Pre-seeded with empty hours so the chart has no holes. Counts EVENT rows (a recipient who opens 3 times shows 3 opens in the hour).","tags":["Campaigns"]}},"/v1/campaigns/{idOrSlug}/variant-stats":{"get":{"operationId":"CampaignsController_variantStats","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"A/B subject-line performance — sent / delivered / opened / clicked counts per variant + open + click rates. Returns variants=[] when the campaign didn't use A/B testing so the dashboard can hide the section.","tags":["Campaigns"]}},"/v1/campaigns/{idOrSlug}/by-isp":{"get":{"operationId":"CampaignsController_byIsp","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"By-recipient-domain breakdown — total / delivered / bounced / failed per domain plus opens, clicks, and derived rates (delivery, bounce, open, click). Used to spot ISP-specific deliverability issues. Capped at 50 domains, ordered by recipient volume desc.","tags":["Campaigns"]}},"/v1/campaigns/{idOrSlug}/duplicate":{"post":{"operationId":"CampaignsController_duplicate","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"security":[{"JWT":[]}],"summary":"Duplicate a campaign into a fresh DRAFT. Copies template / inline body / audience / sender; resets counters and status. A new slug is generated; collisions get a -2, -3 suffix.","tags":["Campaigns"]}},"/v1/campaigns/{idOrSlug}/resend-non-openers":{"post":{"operationId":"CampaignsController_resendNonOpeners","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResendNonOpenersDto"}}}},"responses":{"201":{"description":""}},"security":[{"JWT":[]}],"summary":"Create a DRAFT that re-sends a finished campaign to recipients who were delivered but never opened. Snapshots the non-openers into a fresh list; hard-bounced/unsubscribed are excluded at send time. Unlocks 24h after the source campaign finished sending.","tags":["Campaigns"]}},"/v1/campaigns/{idOrSlug}/schedule":{"post":{"operationId":"CampaignsController_schedule","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduleCampaignDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Move a campaign from DRAFT to SCHEDULED. The future materialiser cron picks it up when `scheduledFor <= now` (or immediately when null).","tags":["Campaigns"]}},"/v1/campaigns/{idOrSlug}/send":{"post":{"operationId":"CampaignsController_send","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Materialise the campaign — render bodies per contact, enqueue Messages. Synchronous for MVP volumes; idempotent on already-SENT campaigns.","tags":["Campaigns"]}},"/v1/campaigns/{idOrSlug}/render-preview":{"post":{"operationId":"CampaignsController_renderPreview","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderPreviewCampaignDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Render the campaign body for one (real or stub) contact, returning the resolved subject + html + text. No message is sent. Used by the detail-page inline preview so customers can sanity-check merge tags + layout.","tags":["Campaigns"]}},"/v1/campaigns/{idOrSlug}/send-test":{"post":{"operationId":"CampaignsController_sendTest","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendTestCampaignDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Send a preview of the rendered campaign to up to 5 user-supplied email addresses. Uses the same render path as the real send but bypasses suppression, compliance blocking and the campaign counters. Subject is prefixed with [TEST]. DRAFT and SCHEDULED only.","tags":["Campaigns"]}},"/v1/campaigns/{idOrSlug}/cancel":{"post":{"operationId":"CampaignsController_cancel","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelCampaignDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Cancel a DRAFT, SCHEDULED, SENDING, or PAUSED campaign. Marks all still-queued Messages as CANCELED in the same transaction so workers drop them on pickup; in-flight SMTP sessions are not aborted.","tags":["Campaigns"]}},"/v1/campaigns/{idOrSlug}/pause":{"post":{"operationId":"CampaignsController_pause","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PauseCampaignDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Pause a SENDING or SCHEDULED campaign. Worker nak's all subsequent send jobs for this campaign with a 30s delay so they stay in queue. Materialisation loop exits at the next 50-recipient checkpoint. Reversible via /resume.","tags":["Campaigns"]}},"/v1/campaigns/{idOrSlug}/resume":{"post":{"operationId":"CampaignsController_resume","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Resume a PAUSED campaign. Flips status back to SCHEDULED (if scheduledFor still in the future) or SENDING (immediate). Workers stop dropping jobs on the next pull. NOTE: if materialisation was incomplete when paused, you may need to POST /send again to enqueue the remaining recipients (Suppression catches accidental double-sends).","tags":["Campaigns"]}},"/v1/internal/campaigns/breaker-tick":{"post":{"operationId":"CampaignsInternalController_tick","parameters":[],"responses":{"200":{"description":""}},"tags":["CampaignsInternal"]}},"/v1/segments":{"post":{"operationId":"ContactSegmentsController_create","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSegmentDto"}}}},"responses":{"201":{"description":""}},"security":[{"JWT":[]}],"summary":"Create a saved segment over Contacts.","tags":["Segments"]},"get":{"operationId":"ContactSegmentsController_list","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"cursor","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"List segments (cursor-paginated).","tags":["Segments"]}},"/v1/segments/{idOrSlug}":{"get":{"operationId":"ContactSegmentsController_get","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Get a segment by id or slug.","tags":["Segments"]},"patch":{"operationId":"ContactSegmentsController_update","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSegmentDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Update a segment's name, description, or definition.","tags":["Segments"]},"delete":{"operationId":"ContactSegmentsController_remove","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Soft-delete a segment.","tags":["Segments"]}},"/v1/segments/{idOrSlug}/preview":{"get":{"operationId":"ContactSegmentsController_previewSaved","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Re-evaluate the segment's current saved definition and return a fresh count + 10-row sample. Useful for refreshing the detail page after the underlying contacts have changed without modifying the definition itself.","tags":["Segments"]}},"/v1/segments/preview":{"post":{"operationId":"ContactSegmentsController_preview","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewSegmentDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Dry-run a segment definition without saving it — returns count + 10-row sample. Used by the editor so customers can iterate on the filter before committing.","tags":["Segments"]}},"/v1/messages":{"post":{"operationId":"MessagesController_send","parameters":[{"name":"X-IXmailer-API-Key","in":"header","description":"API key (alternative to Authorization: Bearer ixk_...)","schema":{"type":"string"}},{"name":"idempotency-key","required":true,"in":"header","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"Optional. Opaque client-generated string (recommended: UUIDv4). Retrying the same request with the same key within 24h returns the original response without enqueuing a duplicate send.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMessageDto"}}}},"responses":{"202":{"description":""}},"security":[{"JWT":[]}],"summary":"Send a transactional email — accepts the message and returns 202","tags":["Messages"]},"get":{"operationId":"MessagesController_list","parameters":[{"name":"X-IXmailer-API-Key","in":"header","description":"API key (alternative to Authorization: Bearer ixk_...)","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"status","required":true,"in":"query","schema":{"type":"string"}},{"name":"campaignId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Recent messages (max 200). Optional filters: ?status=<status> + ?campaignId=<id>","tags":["Messages"]}},"/v1/messages/quota":{"get":{"operationId":"MessagesController_quota","parameters":[{"name":"X-IXmailer-API-Key","in":"header","description":"API key (alternative to Authorization: Bearer ixk_...)","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Current send quota usage for the workspace","tags":["Messages"]}},"/v1/messages/{id}":{"get":{"operationId":"MessagesController_lookup","parameters":[{"name":"X-IXmailer-API-Key","in":"header","description":"API key (alternative to Authorization: Bearer ixk_...)","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Look up a sent message's status","tags":["Messages"]}},"/v1/messages/{id}/cancel":{"post":{"operationId":"MessagesController_cancel","parameters":[{"name":"X-IXmailer-API-Key","in":"header","description":"API key (alternative to Authorization: Bearer ixk_...)","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Emergency-cancel a still-queued or deferred message. Marks the row CANCELED so the worker drops the NATS job on pickup. No-op + 400 if the message is already SENDING/DELIVERED/FAILED/BOUNCED/CANCELED.","tags":["Messages"]}},"/v1/sender-domains":{"post":{"operationId":"SenderDomainsController_register","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterDomainDto"}}}},"responses":{"201":{"description":""}},"security":[{"JWT":[]}],"summary":"Register a sending domain. Generates DKIM keypair and returns the DNS records to add.","tags":["Sender Domains"]},"get":{"operationId":"SenderDomainsController_list","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"List sender domains for the current workspace","tags":["Sender Domains"]}},"/v1/sender-domains/{id}":{"get":{"operationId":"SenderDomainsController_get","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Get one sender domain (includes DNS records)","tags":["Sender Domains"]},"delete":{"operationId":"SenderDomainsController_remove","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Soft-delete (disable) a sender domain","tags":["Sender Domains"]},"patch":{"operationId":"SenderDomainsController_updateSettings","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDomainSettingsDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Update per-domain settings (tracking overrides, message retention). Set a value to `null` to fall back to the workspace default.","tags":["Sender Domains"]}},"/v1/sender-domains/{id}/verify":{"post":{"operationId":"SenderDomainsController_verify","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"security":[{"JWT":[]}],"summary":"Run DNS checks now and update the domain's status","tags":["Sender Domains"]}},"/v1/sender-domains/{id}/tracking-host":{"post":{"operationId":"SenderDomainsController_setTrackingHost","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetTrackingHostDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Register or change the branded tracking hostname for this domain. Customer must add a CNAME at <host> pointing to track.ixmailer.org, then call POST /sender-domains/:id/tracking-host/verify. Hostname must be a subdomain of the sender domain.","tags":["Sender Domains"]},"delete":{"operationId":"SenderDomainsController_removeTrackingHost","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Remove the branded tracking host. Tracking links revert to using api.ixmailer.com on the next send.","tags":["Sender Domains"]}},"/v1/sender-domains/{id}/tracking-host/verify":{"post":{"operationId":"SenderDomainsController_verifyTrackingHost","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Verify the customer's CNAME for the branded tracking host. After the DNS check passes, a Let's Encrypt cert is issued asynchronously; tracking links in outbound mail flip to the branded host once the cert is ready (poll the same endpoint or GET /sender-domains/:id).","tags":["Sender Domains"]}},"/v1/sender-domains/{id}/clear-queue":{"post":{"operationId":"SenderDomainsController_clearQueue","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClearQueueDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Clear in-flight messages for this domain — every QUEUED / SENDING / DEFERRED message is marked FAILED with an explanatory reason. Already-delivered / already-failed messages are untouched.","tags":["Sender Domains"]}},"/v1/sender-domains/{id}/stats":{"get":{"operationId":"SenderDomainsController_stats","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"monthOffset","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Monthly counters for this domain: accepted / delivered / bounced / opened / clicked / complained. ?monthOffset=-1 for previous calendar month.","tags":["Sender Domains"]}},"/v1/sender-domains/{id}/messages":{"get":{"operationId":"SenderDomainsController_listMessages","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"cursor","required":true,"in":"query","schema":{"type":"string"}},{"name":"status","required":true,"in":"query","schema":{"type":"string"}},{"name":"search","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Per-domain message log. Same shape as /v1/messages but pre-filtered to this sender domain. Supports ?status= and ?search= (subject / to / id).","tags":["Sender Domains"]}},"/v1/sender-domains/{id}/api-keys":{"post":{"operationId":"SenderDomainsController_createApiKey","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDomainApiKeyDto"}}}},"responses":{"201":{"description":""}},"security":[{"JWT":[]}],"summary":"Mint a per-domain API key. The key can only send From: *@<this domain>. Returns the plaintext key ONCE — store it now, it won't be shown again.","tags":["Sender Domains"]},"get":{"operationId":"SenderDomainsController_listApiKeys","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"List active per-domain API keys for this domain","tags":["Sender Domains"]}},"/v1/suppressions":{"post":{"operationId":"SuppressionsController_add","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddSuppressionDto"}}}},"responses":{"201":{"description":""}},"security":[{"JWT":[]}],"summary":"Add an address to the workspace suppression list","tags":["Suppressions"]},"get":{"operationId":"SuppressionsController_list","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"cursor","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"List suppressed addresses (paginated by cursor)","tags":["Suppressions"]}},"/v1/suppressions/{id}":{"delete":{"operationId":"SuppressionsController_remove","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Remove an address from the suppression list","tags":["Suppressions"]}},"/v1/suppressions/bulk-import":{"post":{"operationId":"SuppressionsController_bulkImport","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkImportDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Bulk-import suppressions from a CSV/text payload. One email per line; optional `email,reason,source` format. Auto-detects header row. Returns inserted / skippedInvalid / skippedDuplicate counts.","tags":["Suppressions"]}},"/v1/webhook-endpoints":{"post":{"operationId":"WebhooksController_create","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookDto"}}}},"responses":{"201":{"description":""}},"security":[{"JWT":[]}],"summary":"Register a webhook endpoint. Returns the signing secret ONCE — store it; we won't show it again.","tags":["Webhooks"]},"get":{"operationId":"WebhooksController_list","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"List webhook endpoints for this workspace","tags":["Webhooks"]}},"/v1/webhook-endpoints/{id}":{"get":{"operationId":"WebhooksController_get","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Get one webhook endpoint (without secret)","tags":["Webhooks"]},"patch":{"operationId":"WebhooksController_update","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Update url / eventTypes / isActive / description","tags":["Webhooks"]},"delete":{"operationId":"WebhooksController_remove","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Soft-delete a webhook endpoint","tags":["Webhooks"]}},"/v1/webhook-endpoints/{id}/deliveries":{"get":{"operationId":"WebhooksController_deliveries","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Recent delivery attempts for this endpoint","tags":["Webhooks"]}},"/v1/webhook-endpoints/{id}/deliveries/{deliveryId}":{"get":{"operationId":"WebhooksController_getDelivery","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"deliveryId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Full delivery record including the JSON payload we sent and the response body we got back. Used by the dashboard's per-delivery debugger panel.","tags":["Webhooks"]}},"/v1/webhook-endpoints/{id}/deliveries/{deliveryId}/retry":{"post":{"operationId":"WebhooksController_retryDelivery","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"deliveryId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Manually re-queue a PENDING or ABANDONED delivery for immediate retry. Already-DELIVERED deliveries can't be retried (would double-fire the receiver).","tags":["Webhooks"]}},"/v1/webhook-endpoints/{id}/test":{"post":{"operationId":"WebhooksController_sendTest","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"202":{"description":""}},"security":[{"JWT":[]}],"summary":"Send a synthetic `webhook.test` event to this endpoint so the customer can verify their integration.","tags":["Webhooks"]}},"/v1/p/preferences":{"get":{"operationId":"PreferencesController_get","parameters":[{"name":"t","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"summary":"Read a recipient's current list memberships + workspace branding for the preference page. Token-authed (same HMAC token as one-click unsubscribe).","tags":["Public preferences"]},"post":{"operationId":"PreferencesController_update","parameters":[{"name":"t","required":true,"in":"query","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePreferencesDto"}}}},"responses":{"200":{"description":""}},"summary":"Apply a preferences update. subscribedListSlugs is the desired end state — membership in lists outside the set gets removed. Empty set + existing memberships triggers a full unsubscribe (suppression entry).","tags":["Public preferences"]}},"/v1/p/preferences/unsubscribe-all":{"post":{"operationId":"PreferencesController_unsubscribeAll","parameters":[{"name":"t","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"summary":"Escape hatch — remove the recipient from every workspace list AND add a suppression entry. Same downstream effect as the legacy one-click POST.","tags":["Public preferences"]}},"/v1/templates":{"post":{"operationId":"TemplatesController_create","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTemplateDto"}}}},"responses":{"201":{"description":""}},"security":[{"JWT":[]}],"summary":"Create a reusable template.","tags":["Templates"]},"get":{"operationId":"TemplatesController_list","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"cursor","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"List templates (cursor-paginated).","tags":["Templates"]}},"/v1/templates/{idOrSlug}":{"get":{"operationId":"TemplatesController_get","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Get a template by id or slug.","tags":["Templates"]},"patch":{"operationId":"TemplatesController_update","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTemplateDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Update a template. Edits do NOT affect already-sent campaign messages (those captured the rendered body at send time).","tags":["Templates"]},"delete":{"operationId":"TemplatesController_remove","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Soft-delete a template. Campaigns referencing it become orphaned.","tags":["Templates"]}},"/v1/templates/{idOrSlug}/preview":{"post":{"operationId":"TemplatesController_preview","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewTemplateDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Render the template against a supplied variable bag. Used by the dashboard's preview pane to show what a contact will actually see.","tags":["Templates"]}},"/v1/templates/preview-inline":{"post":{"operationId":"TemplatesController_previewInline","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewInlineDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Render an unsaved template body inline. The editor calls this on every (debounced) edit so the customer sees a live preview without having to save first. No DB read; just runs the merge-tag renderer against the supplied subject/html/text + vars.","tags":["Templates"]}},"/v1/contact-lists":{"post":{"operationId":"ContactListsController_create","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContactListDto"}}}},"responses":{"201":{"description":""}},"security":[{"JWT":[]}],"summary":"Create a contact list. Slug is auto-derived from name when omitted.","tags":["Contact lists"]},"get":{"operationId":"ContactListsController_list","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"cursor","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"List contact lists (cursor-paginated).","tags":["Contact lists"]}},"/v1/contact-lists/{idOrSlug}":{"get":{"operationId":"ContactListsController_get","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Get a list by id or slug.","tags":["Contact lists"]},"patch":{"operationId":"ContactListsController_update","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateContactListDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Update a list's name or description.","tags":["Contact lists"]},"delete":{"operationId":"ContactListsController_remove","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Soft-delete a list. Existing memberships are preserved.","tags":["Contact lists"]}},"/v1/contact-lists/{idOrSlug}/members":{"get":{"operationId":"ContactListsController_listMembers","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"cursor","required":true,"in":"query","schema":{"type":"string"}},{"name":"search","required":true,"in":"query","schema":{"type":"string"}},{"name":"status","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"List contacts belonging to this list (cursor-paginated). Optional ?search= substring matches email/firstName/lastName. ?status= narrows to a single contact status.","tags":["Contact lists"]},"post":{"operationId":"ContactListsController_addMember","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddMemberDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Add a contact to this list by contactId. Idempotent — re-adding returns `alreadyMember: true`.","tags":["Contact lists"]}},"/v1/contact-lists/{idOrSlug}/stats":{"get":{"operationId":"ContactListsController_stats","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Per-status member counts (total / subscribed / unsubscribed / cleaned / pending). Drives the stats panel on the list-detail page.","tags":["Contact lists"]}},"/v1/contact-lists/{idOrSlug}/export.csv":{"get":{"operationId":"ContactListsController_exportCsv","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Download every member of this list as CSV. Format mirrors what POST /v1/contacts/bulk-import accepts so export → import is a lossless round-trip. Includes any custom-attribute columns observed across the list. Capped at 100k rows per call.","tags":["Contact lists"]}},"/v1/contact-lists/{idOrSlug}/members/bulk-action":{"post":{"operationId":"ContactListsController_bulkAction","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkMembersActionDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Bulk action on members: action=remove drops the list-membership row(s) only (contact stays in the workspace); action=unsubscribe sets contact.status=UNSUBSCRIBED globally; action=resubscribe flips status back to SUBSCRIBED AND clears the UNSUBSCRIBE/MANUAL suppression rows (hard-bounce/complaint suppressions are left). Capped at 1000 contactIds per call.","tags":["Contact lists"]}},"/v1/contact-lists/{idOrSlug}/members/{contactId}":{"delete":{"operationId":"ContactListsController_removeMember","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrSlug","required":true,"in":"path","schema":{"type":"string"}},{"name":"contactId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Remove a contact from this list. No-op if not a member.","tags":["Contact lists"]}},"/v1/p/lists/{slug}":{"get":{"operationId":"PublicSubscribeController_getList","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"summary":"Read minimal list metadata for the hosted signup form: workspace branding + the list's name + description. 404 when publicSubscriptionEnabled is off.","tags":["Public subscribe"]}},"/v1/p/lists/{slug}/subscribe":{"post":{"operationId":"PublicSubscribeController_subscribe","parameters":[{"name":"slug","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicSubscribeDto"}}}},"responses":{"200":{"description":""}},"summary":"Add a recipient to a public list. Captures consent IP + User-Agent. Idempotent — re-subscribing an existing contact merges name fields and re-adds the list membership if it had been removed.","tags":["Public subscribe"]}},"/v1/contacts":{"post":{"operationId":"ContactsController_create","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContactDto"}}}},"responses":{"201":{"description":""}},"security":[{"JWT":[]}],"summary":"Create or update a contact. Email is the natural key — re-posting the same email updates the editable fields (status is preserved).","tags":["Contacts"]},"get":{"operationId":"ContactsController_list","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"limit","required":true,"in":"query","schema":{"type":"string"}},{"name":"cursor","required":true,"in":"query","schema":{"type":"string"}},{"name":"status","required":true,"in":"query","schema":{"type":"string"}},{"name":"search","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"List contacts (paginated by cursor). Optional ?status= and ?search=.","tags":["Contacts"]}},"/v1/contacts/count":{"get":{"operationId":"ContactsController_count","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"status","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Get the count of contacts in the workspace (optionally filtered by status).","tags":["Contacts"]}},"/v1/contacts/{idOrEmail}":{"get":{"operationId":"ContactsController_get","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"idOrEmail","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Get a contact by id (`cuid`) or by email address (auto-detected by `@`).","tags":["Contacts"]}},"/v1/contacts/{id}":{"patch":{"operationId":"ContactsController_update","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateContactDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Update a contact's editable fields.","tags":["Contacts"]},"delete":{"operationId":"ContactsController_remove","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Soft-delete a contact. Messages already sent are retained for audit. For a GDPR Article 17 hard-delete, contact support.","tags":["Contacts"]}},"/v1/contacts/bulk-action":{"post":{"operationId":"ContactsController_bulkAction","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkActionContactsDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Apply a single action (delete / unsubscribe / add-to-list / remove-from-list) to up to 500 contacts in one round-trip. Drives the selection toolbar on the /contacts dashboard. Cross-workspace ids are silently ignored.","tags":["Contacts"]}},"/v1/contacts/bulk-import":{"post":{"operationId":"ContactsController_bulkImport","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkImportContactsDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Bulk-import contacts from a CSV/TSV payload. Auto-detects header row. Optional listSlug attaches every imported contact to that list. Returns inserted/updated/skippedInvalid counts.","tags":["Contacts"]}},"/v1/dashboard/summary":{"get":{"operationId":"DashboardController_summary","parameters":[],"responses":{"200":{"description":""}},"security":[{"JWT":[]},{"cookie":[]}],"summary":"Single round-trip dashboard payload: send volumes, daily sparkline, quota usage, domain/key/suppression counts.","tags":["Dashboard"]}},"/v1/dashboard/live":{"get":{"operationId":"DashboardController_live","parameters":[],"responses":{"200":{"description":""}},"security":[{"JWT":[]},{"cookie":[]}],"summary":"Live monitoring snapshot — polled every ~5s by the /live page. Returns current queue depth, per-minute send rate (last 60 min), active campaigns with auto-pause flags, and top-10 active sending IPs with utilization vs daily cap.","tags":["Dashboard"]}},"/v1/sender-domains/{id}/credentials":{"post":{"operationId":"DomainCredentialsController_create","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDomainCredentialDto"}}}},"responses":{"201":{"description":""}},"security":[{"JWT":[]}],"summary":"Create an SMTP credential for this verified domain. Returns the plaintext password ONCE — store it now, you won't see it again.","tags":["Sender domains"]},"get":{"operationId":"DomainCredentialsController_list","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"List credentials for this domain. No plaintext.","tags":["Sender domains"]}},"/v1/sender-domains/{id}/credentials/{credentialId}/rotate":{"post":{"operationId":"DomainCredentialsController_rotate","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"credentialId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Rotate the credential's password. Returns the new plaintext ONCE. The old password stops working at the next AUTH (in-flight sessions keep working).","tags":["Sender domains"]}},"/v1/sender-domains/{id}/credentials/{credentialId}":{"delete":{"operationId":"DomainCredentialsController_revoke","parameters":[{"name":"X-IXmailer-API-Key","in":"header","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","schema":{"type":"string"}},{"name":"credentialId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeDomainCredentialDto"}}}},"responses":{"200":{"description":""}},"security":[{"JWT":[]}],"summary":"Revoke a credential. Soft delete — the row stays for audit; future AUTH attempts return INVALID_CREDENTIALS.","tags":["Sender domains"]}},"/v1/inbound-domains":{"get":{"operationId":"InboundDomainsController_list","parameters":[],"responses":{"200":{"description":""}},"tags":["inbound-domains"]},"post":{"operationId":"InboundDomainsController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInboundDomainDto"}}}},"responses":{"201":{"description":""}},"tags":["inbound-domains"]}},"/v1/inbound-domains/{id}":{"get":{"operationId":"InboundDomainsController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["inbound-domains"]},"delete":{"operationId":"InboundDomainsController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["inbound-domains"]}},"/v1/inbound-domains/{id}/verify":{"post":{"operationId":"InboundDomainsController_verify","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["inbound-domains"]}},"/v1/inbound-messages":{"get":{"operationId":"InboundMessagesController_list","parameters":[],"responses":{"200":{"description":""}},"tags":["inbound-messages"]}},"/v1/inbound-messages/{id}":{"get":{"operationId":"InboundMessagesController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["inbound-messages"]}},"/v1/routes":{"get":{"operationId":"RoutesController_list","parameters":[],"responses":{"200":{"description":""}},"tags":["routes"]},"post":{"operationId":"RoutesController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRouteDto"}}}},"responses":{"201":{"description":""}},"tags":["routes"]}},"/v1/routes/{id}":{"get":{"operationId":"RoutesController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["routes"]},"patch":{"operationId":"RoutesController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRouteDto"}}}},"responses":{"200":{"description":""}},"tags":["routes"]},"delete":{"operationId":"RoutesController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["routes"]}},"/v1/me":{"patch":{"operationId":"SettingsController_updateProfile","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProfileDto"}}}},"responses":{"200":{"description":""}},"security":[{"cookie":[]}],"summary":"Update the current user's profile","tags":["Settings"]}},"/v1/me/password":{"post":{"operationId":"SettingsController_changePassword","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordDto"}}}},"responses":{"200":{"description":""}},"security":[{"cookie":[]}],"summary":"Change the current user's password. Revokes every OTHER session on success — the requesting session stays alive.","tags":["Settings"]}},"/v1/me/ops-password":{"post":{"operationId":"SettingsController_setOpsPassword","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetOpsPasswordDto"}}}},"responses":{"200":{"description":""}},"security":[{"cookie":[]}],"summary":"Set / rotate the operator-panel password. Distinct from the customer password used here; this is what ops.ixmailer.com asks for. Requires confirmation with the current customer password. Operator-only.","tags":["Settings"]}},"/v1/me/sessions":{"get":{"operationId":"SettingsController_listSessions","parameters":[],"responses":{"200":{"description":""}},"security":[{"cookie":[]}],"summary":"List active sessions for the current user","tags":["Settings"]}},"/v1/me/sessions/{id}":{"delete":{"operationId":"SettingsController_revokeSession","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"security":[{"cookie":[]}],"summary":"Revoke a specific session","tags":["Settings"]}},"/v1/workspaces/current":{"get":{"operationId":"SettingsController_getCurrentWorkspace","parameters":[],"responses":{"200":{"description":""}},"security":[{"cookie":[]}],"summary":"Get the current workspace's settings","tags":["Settings"]},"patch":{"operationId":"SettingsController_updateCurrentWorkspace","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWorkspaceDto"}}}},"responses":{"200":{"description":""}},"security":[{"cookie":[]}],"summary":"Update the current workspace (Owner/Admin only)","tags":["Settings"]}},"/v1/workspaces/current/kyc/request":{"post":{"operationId":"SettingsController_requestKycUpgrade","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestKycUpgradeDto"}}}},"responses":{"200":{"description":""}},"security":[{"cookie":[]}],"summary":"Request a KYC tier upgrade. Flips workspace status to PENDING_REVIEW; an operator follows up via the contact email on the workspace.","tags":["Settings"]}},"/v1/workspaces/current/members":{"get":{"operationId":"TeamController_listMembers","parameters":[],"responses":{"200":{"description":""}},"security":[{"cookie":[]}],"summary":"List members of the current workspace","tags":["Team"]}},"/v1/workspaces/current/members/{userId}":{"patch":{"operationId":"TeamController_changeRole","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeRoleDto"}}}},"responses":{"200":{"description":""}},"security":[{"cookie":[]}],"summary":"Change a member's role (Owner only)","tags":["Team"]},"delete":{"operationId":"TeamController_removeMember","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"security":[{"cookie":[]}],"summary":"Remove a member from the current workspace","tags":["Team"]}},"/v1/workspaces/current/invitations":{"get":{"operationId":"TeamController_listInvitations","parameters":[],"responses":{"200":{"description":""}},"security":[{"cookie":[]}],"summary":"List pending invitations for the current workspace","tags":["Team"]},"post":{"operationId":"TeamController_invite","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteDto"}}}},"responses":{"201":{"description":""}},"security":[{"cookie":[]}],"summary":"Invite a teammate. Returns a one-time share link (token) the inviter sends via their own channel. Tokens expire in 7 days.","tags":["Team"]}},"/v1/workspaces/current/invitations/{id}":{"delete":{"operationId":"TeamController_revokeInvitation","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""}},"security":[{"cookie":[]}],"summary":"Revoke a pending invitation","tags":["Team"]}},"/v1/invitations/{token}":{"get":{"operationId":"TeamController_preview","parameters":[{"name":"token","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"cookie":[]}],"summary":"Preview an invitation by its share-token. Returns the workspace name, role, and inviter without requiring auth — so the invitee sees who invited them BEFORE creating an account.","tags":["Team"]}},"/v1/invitations/{token}/accept":{"post":{"operationId":"TeamController_accept","parameters":[{"name":"token","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"security":[{"cookie":[]}],"summary":"Accept an invitation. The caller must already be authenticated; sign up first if not. We don't auto-bind the email — the caller's existing account joins the inviter's workspace regardless of whose email the invitation was sent to.","tags":["Team"]}}},"info":{"title":"IXmailer API","description":"Bulk email + transactional SaaS — public REST API.\n\nAuthentication: pass a workspace API key in the `Authorization: Bearer ixk_…` header (or `X-IXmailer-API-Key`). Per-domain keys minted at `POST /v1/sender-domains/:id/api-keys` are accepted on the same header but can only send `From: *@<their-domain>`.\n\nErrors: every non-2xx response is `{ error: { code, message, details?, requestId, timestamp } }`. The `code` is stable and machine-readable.","version":"0.0.1","contact":{"name":"Innovatex Digital FZCO","url":"https://ixmailer.com","email":"support@ixmailer.com"},"license":{"name":"Proprietary","url":"https://ixmailer.com/legal/terms"}},"tags":[],"servers":[{"url":"https://api.ixmailer.com","description":"Production"}],"components":{"securitySchemes":{"ApiKeyBearer":{"scheme":"bearer","bearerFormat":"ixk_…","type":"http"},"ApiKey":{"type":"apiKey","in":"header","name":"X-IXmailer-API-Key"}},"schemas":{"SignupDto":{"type":"object","properties":{"email":{"type":"string","example":"founder@startup.com"},"password":{"type":"string","example":"correct horse battery","minLength":8,"maxLength":256},"fullName":{"type":"string","example":"Mehdi Aghazadeh"},"workspaceName":{"type":"string","example":"Acme Inc.","description":"Display name for the initial workspace"},"workspaceSlug":{"type":"string","example":"acme","description":"URL slug for the workspace. Lowercase alphanumeric + hyphens. Must be unique."},"phoneVerificationId":{"type":"string","description":"Id returned by POST /auth/signup/phone/verify. Required once SMS OTP is mandatory."},"aupAccepted":{"type":"boolean","example":true,"description":"Must be true to accept the Acceptable Use Policy linked in the signup form."},"companyName":{"type":"string","example":"Acme Inc."},"industry":{"type":"string","enum":["saas","ecommerce","b2b","internal_hr_it","education","healthcare_clinic","nonprofit","real_estate","recruiting","professional_services","marketing_agency","lead_generation","affiliate_marketing","online_course","dating_mainstream","debt_advisory_licensed","mlm","forex_ib","prop_trading","crypto_exchange","web3_defi","trading_signals","stock_broker","fintech_consumer","regulated_gambling","supplements_cbd","sexual_wellness","other"],"description":"Industry dropdown from the signup form. Maps directly to AUP §3 tier."},"websiteUrl":{"type":"string","example":"https://example.com"},"useCase":{"type":"string","example":"Transactional receipts + a weekly customer digest"},"expectedMonthlyVolume":{"type":"number","example":50000,"minimum":0,"maximum":1000000000},"turnstileToken":{"type":"string"}},"required":["email","password","fullName","workspaceName","workspaceSlug"]},"LoginDto":{"type":"object","properties":{"email":{"type":"string","example":"founder@startup.com"},"password":{"type":"string","example":"correct horse battery staple"}},"required":["email","password"]},"MfaLoginDto":{"type":"object","properties":{}},"ConfirmEnrollDto":{"type":"object","properties":{}},"DisableDto":{"type":"object","properties":{}},"FinishSignupDto":{"type":"object","properties":{}},"StartPhoneVerifyDto":{"type":"object","properties":{}},"VerifyPhoneDto":{"type":"object","properties":{}},"MarkReadDto":{"type":"object","properties":{}},"CreateApiKeyDto":{"type":"object","properties":{}},"StartCheckoutDto":{"type":"object","properties":{}},"CreatePortalDto":{"type":"object","properties":{}},"CheckoutDto":{"type":"object","properties":{}},"CreateCampaignDto":{"type":"object","properties":{}},"UpdateCampaignDto":{"type":"object","properties":{}},"ResendNonOpenersDto":{"type":"object","properties":{}},"ScheduleCampaignDto":{"type":"object","properties":{}},"RenderPreviewCampaignDto":{"type":"object","properties":{}},"SendTestCampaignDto":{"type":"object","properties":{}},"CancelCampaignDto":{"type":"object","properties":{}},"PauseCampaignDto":{"type":"object","properties":{}},"CreateSegmentDto":{"type":"object","properties":{}},"PreviewSegmentDto":{"type":"object","properties":{}},"UpdateSegmentDto":{"type":"object","properties":{}},"SendMessageDto":{"type":"object","properties":{}},"RegisterDomainDto":{"type":"object","properties":{}},"SetTrackingHostDto":{"type":"object","properties":{}},"ClearQueueDto":{"type":"object","properties":{}},"UpdateDomainSettingsDto":{"type":"object","properties":{}},"CreateDomainApiKeyDto":{"type":"object","properties":{}},"AddSuppressionDto":{"type":"object","properties":{}},"BulkImportDto":{"type":"object","properties":{}},"CreateWebhookDto":{"type":"object","properties":{}},"UpdateWebhookDto":{"type":"object","properties":{}},"UpdatePreferencesDto":{"type":"object","properties":{}},"CreateTemplateDto":{"type":"object","properties":{}},"UpdateTemplateDto":{"type":"object","properties":{}},"PreviewTemplateDto":{"type":"object","properties":{}},"PreviewInlineDto":{"type":"object","properties":{}},"CreateContactListDto":{"type":"object","properties":{}},"UpdateContactListDto":{"type":"object","properties":{}},"AddMemberDto":{"type":"object","properties":{}},"BulkMembersActionDto":{"type":"object","properties":{}},"PublicSubscribeDto":{"type":"object","properties":{}},"CreateContactDto":{"type":"object","properties":{}},"UpdateContactDto":{"type":"object","properties":{}},"BulkActionContactsDto":{"type":"object","properties":{}},"BulkImportContactsDto":{"type":"object","properties":{}},"CreateDomainCredentialDto":{"type":"object","properties":{}},"RevokeDomainCredentialDto":{"type":"object","properties":{}},"CreateInboundDomainDto":{"type":"object","properties":{}},"CreateRouteDto":{"type":"object","properties":{}},"UpdateRouteDto":{"type":"object","properties":{}},"UpdateProfileDto":{"type":"object","properties":{}},"ChangePasswordDto":{"type":"object","properties":{}},"SetOpsPasswordDto":{"type":"object","properties":{}},"UpdateWorkspaceDto":{"type":"object","properties":{}},"RequestKycUpgradeDto":{"type":"object","properties":{}},"ChangeRoleDto":{"type":"object","properties":{}},"InviteDto":{"type":"object","properties":{}}}}}