{"openapi":"3.0.3","info":{"title":"wemail API","description":"Transactional email delivery API","version":"3.0","contact":{"name":"wemail support","url":"https://wemail.io/docs","email":"support@wemail.io"},"license":{"name":"Proprietary","url":"https://wemail.io/terms"}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key or JWT token (Bearer <token>)"}},"schemas":{"AccountSummary":{"type":"object","properties":{"id":{"type":"string","description":"Account ID"},"name":{"type":"string","nullable":true,"description":"Account display name"},"plan":{"type":"string","enum":["free","professional",null],"nullable":true,"description":"Billing plan"},"status":{"type":"string","enum":["active","suspended","past_due","trialing","trashed",null],"nullable":true,"description":"Account status"}},"required":["id","name","plan","status"],"additionalProperties":false},"ApiKey":{"type":"object","properties":{"id":{"type":"string","description":"API key ID"},"name":{"type":"string","description":"Key name"},"prefix":{"type":"string","description":"Key prefix (the full key is never stored or returned again)"},"scope":{"type":"string","enum":["full_access","send_only","read_only"],"description":"Key scope"},"env":{"type":"string","enum":["live","test"],"description":"Key environment"},"verify_before_send":{"type":"boolean","nullable":true,"description":"Verify recipients before sending (null = inherit the domain/account setting)"},"test_outcomes":{"$ref":"#/components/schemas/ApiKeyTestOutcomes"},"ip_allowlist":{"type":"array","items":{"$ref":"#/components/schemas/IpAllowlistEntry"},"description":"Source-IP allowlist for this key (empty = no restriction)"},"last_used_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of last use (null if never used)","nullable":true},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp"}},"required":["id","name","prefix","scope","env","verify_before_send","test_outcomes","ip_allowlist","last_used_at","created_at"],"additionalProperties":false},"ApiKeyCreated":{"type":"object","properties":{"id":{"type":"string","description":"API key ID"},"name":{"type":"string","description":"Key name"},"prefix":{"type":"string","description":"Display prefix of the key"},"scope":{"type":"string","enum":["full_access","send_only","read_only"],"description":"Key scope"},"env":{"type":"string","enum":["live","test"],"description":"Key environment"},"key":{"type":"string","description":"The full API key secret — shown only once at creation time; store it securely"},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp"}},"required":["id","name","prefix","scope","env","key","created_at"],"additionalProperties":false},"ApiKeyDetail":{"type":"object","properties":{"id":{"type":"string","description":"API key ID"},"name":{"type":"string","description":"Key name"},"prefix":{"type":"string","description":"Key prefix"},"scope":{"type":"string","enum":["full_access","send_only","read_only"],"description":"Key scope"},"env":{"type":"string","enum":["live","test"],"description":"Key environment"},"verify_before_send":{"type":"boolean","nullable":true,"description":"Verify recipients before sending (null = inherit the domain/account setting)"},"test_outcomes":{"$ref":"#/components/schemas/ApiKeyTestOutcomes"},"ip_allowlist":{"type":"array","items":{"$ref":"#/components/schemas/IpAllowlistEntry"},"description":"Source-IP allowlist for this key (empty = no restriction)"}},"required":["id","name","prefix","scope","env","verify_before_send","test_outcomes","ip_allowlist"],"additionalProperties":false},"ApiKeyListPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"},"description":"Page of API keys"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["items","pagination"],"additionalProperties":false},"ApiKeyRotated":{"type":"object","properties":{"id":{"type":"string","description":"API key ID (unchanged by rotation)"},"name":{"type":"string","description":"Key name"},"prefix":{"type":"string","description":"New display prefix of the rotated key"},"scope":{"type":"string","enum":["full_access","send_only","read_only"],"description":"Key scope"},"env":{"type":"string","enum":["live","test"],"description":"Key environment"},"key":{"type":"string","description":"The new full API key secret — shown only once at rotation time; store it securely. The previous secret stops working immediately."},"verify_before_send":{"type":"boolean","nullable":true,"description":"Verify recipients before sending (null = inherit the domain/account setting)"},"test_outcomes":{"$ref":"#/components/schemas/ApiKeyTestOutcomes"},"ip_allowlist":{"type":"array","items":{"$ref":"#/components/schemas/IpAllowlistEntry"},"description":"Source-IP allowlist for this key (empty = no restriction)"},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp"}},"required":["id","name","prefix","scope","env","key","verify_before_send","test_outcomes","ip_allowlist","created_at"],"additionalProperties":false},"ApiKeySummary":{"type":"object","properties":{"id":{"type":"string","nullable":true,"description":"API key ID (empty string when authenticated via console JWT)"},"scope":{"type":"string","enum":["full_access","send_only","read_only",null],"nullable":true,"description":"Key scope"},"environment":{"type":"string","enum":["live","test",null],"nullable":true,"description":"Key environment"}},"required":["id","scope","environment"],"additionalProperties":false},"ApiKeyTestOutcomes":{"type":"object","properties":{"send":{"type":"object","additionalProperties":{"type":"number"},"description":"Weighted simulated send outcomes (e.g. delivered/bounce/complaint)"},"verify":{"type":"object","additionalProperties":{"type":"number"},"description":"Weighted simulated verification outcomes"}},"additionalProperties":false,"nullable":true,"description":"Test-key sandbox outcome mix (always null for live keys)"},"AuditLogEntry":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Audit entry ID"},"account_id":{"type":"string","nullable":true,"description":"Account the entry belongs to"},"actor_id":{"type":"string","nullable":true,"description":"Actor ID (null for system actions and redacted staff entries)"},"actor_type":{"type":"string","enum":["user","staff","system"],"description":"Who performed the action"},"actor_email":{"type":"string","description":"Present on staff entries only — always redacted to 'wemail Support'"},"action":{"type":"string","description":"Action performed (e.g. 'key.created')"},"target":{"type":"string","nullable":true,"description":"Affected resource identifier"},"details":{"type":"string","nullable":true,"description":"Free-text details"},"ip_address":{"type":"string","nullable":true,"description":"Origin IP (null for redacted staff entries)"},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp"}},"required":["id","account_id","actor_id","actor_type","action","target","details","ip_address","created_at"],"additionalProperties":false,"description":"Audit log entry"},"AuditLogPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AuditLogEntry"},"description":"Page of results"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["items","pagination"],"additionalProperties":false},"BatchRejectedRecipient":{"type":"object","properties":{"index":{"type":"integer","description":"Zero-based index in the submitted recipients array"},"to":{"type":"string","description":"The rejected address as submitted (trimmed)"},"reason":{"type":"string","description":"Why this recipient was skipped"}},"required":["index","to","reason"],"additionalProperties":false},"BatchSendResult":{"type":"object","properties":{"batch_id":{"type":"string"},"accepted":{"type":"integer","description":"Number of recipients accepted for delivery"},"rejected":{"type":"integer","description":"Number of recipients rejected"},"message_ids":{"type":"array","items":{"type":"string"}},"rejected_recipients":{"type":"array","items":{"$ref":"#/components/schemas/BatchRejectedRecipient"},"description":"The first 100 rejected recipients with reasons — the rest of the batch is sent normally"}},"required":["batch_id","accepted","rejected","message_ids"],"additionalProperties":false},"BillingPeriod":{"type":"object","properties":{"start":{"type":"string","format":"date-time","description":"Start of the current billing period (ISO 8601)"},"end":{"type":"string","format":"date-time","description":"End of the current billing period (ISO 8601) — counters reset here"}},"required":["start","end"],"additionalProperties":false,"description":"The calendar-month window the email counters apply to"},"BounceBreakdown":{"type":"object","properties":{"hard":{"type":"integer","description":"Permanent bounces (excluding suppression blocks)"},"soft":{"type":"integer","description":"Transient bounces"},"suppressed":{"type":"integer","description":"Blocked pre-send: recipient on the account suppression list or unsubscribed"},"undetermined":{"type":"integer"},"hard_rate":{"type":"number","nullable":true,"description":"0–1 fraction; null when the denominator is 0"},"soft_rate":{"type":"number","nullable":true,"description":"0–1 fraction; null when the denominator is 0"},"suppressed_rate":{"type":"number","nullable":true,"description":"0–1 fraction; null when the denominator is 0"},"undetermined_rate":{"type":"number","nullable":true,"description":"0–1 fraction; null when the denominator is 0"}},"required":["hard","soft","suppressed","undetermined","hard_rate","soft_rate","suppressed_rate","undetermined_rate"],"additionalProperties":false,"description":"Bounce classification; class rates share the bounce_rate denominator (sent)"},"BulkSuppressionDeleteResult":{"type":"object","properties":{"removed":{"type":"integer"},"not_found":{"type":"integer"}},"required":["removed","not_found"],"additionalProperties":false},"BulkSuppressionResult":{"type":"object","properties":{"added":{"type":"integer","description":"Entries written or already present (idempotent adds count)"},"skipped":{"type":"array","items":{"$ref":"#/components/schemas/BulkSuppressionSkipped"},"description":"Rows that were not written, with why"}},"required":["added","skipped"],"additionalProperties":false},"BulkSuppressionSkipped":{"type":"object","properties":{"index":{"type":"integer","description":"Zero-based index in the submitted entries array"},"email":{"type":"string"},"reason":{"type":"string","enum":["invalid_syntax","duplicate_in_batch"]}},"required":["index","email","reason"],"additionalProperties":false},"BulkVerificationJob":{"type":"object","properties":{"job_id":{"type":"string"},"status":{"type":"string","enum":["pending","uploading","queued","processing","catch_all_processing","packaging","completed","failed","expired","deleted","paused"],"description":"Job lifecycle status"},"filename":{"type":"string"},"total_count":{"type":"integer"},"processed_count":{"type":"integer"},"summary":{"$ref":"#/components/schemas/BulkVerificationSummary"},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp"},"completed_at":{"type":"string","format":"date-time","nullable":true,"description":"Completion time (ISO 8601), or null while running"},"webhook_url":{"type":"string","format":"uri","nullable":true,"description":"Callback URL the signed result is POSTed to when the job completes, or null"},"error_message":{"type":"string","nullable":true},"can_retry":{"type":"boolean"}},"required":["job_id","status","filename","total_count","processed_count","summary","created_at","completed_at","error_message","can_retry"],"additionalProperties":false},"BulkVerificationJobAccepted":{"type":"object","properties":{"job_id":{"type":"string"},"status":{"type":"string","enum":["pending","uploading","queued","processing","catch_all_processing","packaging","completed","failed","expired","deleted","paused"],"description":"Initial job status (test-mode jobs complete synchronously)"},"total_count":{"type":"integer"},"message":{"type":"string"}},"required":["job_id","status","total_count","message"],"additionalProperties":false},"BulkVerificationResultsPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ValidationResult"}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer"}},"required":["items","total","page","limit"],"additionalProperties":false},"BulkVerificationSummary":{"type":"object","properties":{"valid":{"type":"integer"},"invalid":{"type":"integer"},"catch_all":{"type":"integer"},"unknown":{"type":"integer"},"do_not_mail":{"type":"integer"},"spamtrap":{"type":"integer"},"abuse":{"type":"integer"}},"required":["valid","invalid","catch_all","unknown","do_not_mail","spamtrap","abuse"],"additionalProperties":false},"DailyQuota":{"type":"object","properties":{"limit":{"type":"number","description":"Daily email ceiling for this plan"},"used":{"type":"number","description":"Emails sent today"},"remaining":{"type":"number","description":"Emails left today"}},"required":["limit","used","remaining"],"additionalProperties":false,"nullable":true,"description":"Daily throttle; null when the plan has no daily cap"},"DataRemoval":{"type":"object","properties":{"removal_id":{"type":"string"},"status":{"type":"string","enum":["pending","processing","done","failed"]},"email":{"type":"string"},"counts":{"$ref":"#/components/schemas/DataRemovalCounts"},"created_at":{"type":"string"},"completed_at":{"type":"string","nullable":true}},"required":["removal_id","status","email","counts","created_at","completed_at"],"additionalProperties":false},"DataRemovalAccepted":{"type":"object","properties":{"removal_id":{"type":"string","description":"Poll GET /v3/data-removals/{id}"},"status":{"type":"string","enum":["pending"]}},"required":["removal_id","status"],"additionalProperties":false},"DataRemovalCounts":{"type":"object","properties":{"messages":{"type":"integer"},"events":{"type":"integer"},"verifications":{"type":"integer"},"upload_downloads":{"type":"integer"},"dek_tombstoned":{"type":"integer","description":"1 when this request tombstoned the recipient's encryption key (0 = already erased or account key erased)"},"body_deks_destroyed":{"type":"integer","description":"Message body keys destroyed because every recipient of the message is now erased"},"exports_purged":{"type":"integer","description":"Completed export files purged because they embedded the address"},"already_erased":{"type":"integer","description":"1 when the subject had already been erased before this request"}},"required":["messages","events","verifications","upload_downloads"],"additionalProperties":false,"nullable":true,"description":"Per-table redaction counts — set once status is done"},"DeleteConfirmation":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false},"DeliveryMetrics":{"type":"object","properties":{"sent":{"type":"integer"},"delivered":{"type":"integer"},"bounced":{"type":"integer"},"bounces":{"$ref":"#/components/schemas/BounceBreakdown"},"complained":{"type":"integer"},"delayed":{"type":"integer","description":"Transient delivery delays (deferrals)"},"rejected":{"type":"integer"},"failed":{"type":"integer"},"delivery_rate":{"type":"number","nullable":true,"description":"delivered / sent"},"bounce_rate":{"type":"number","nullable":true,"description":"bounced / sent"},"complaint_rate":{"type":"number","nullable":true,"description":"complained / sent"}},"required":["sent","delivered","bounced","bounces","complained","delayed","rejected","failed","delivery_rate","bounce_rate","complaint_rate"],"additionalProperties":false},"DnsRecord":{"type":"object","properties":{"type":{"type":"string","enum":["TXT","CNAME","MX"]},"host":{"type":"string"},"value":{"type":"string"},"ok":{"type":"boolean"}},"required":["type","host","value","ok"],"additionalProperties":false},"Domain":{"type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"status":{"type":"string","enum":["verified","pending","unverified","partially_verified","verification_failed","verification_timeout"],"description":"Domain verification status"},"spf":{"type":"boolean"},"dkim":{"type":"boolean"},"dmarc":{"type":"boolean"},"region":{"type":"string","nullable":true},"dkim_selector":{"type":"string","nullable":true},"tracking_subdomain":{"type":"string","nullable":true},"dns_records":{"type":"array","items":{"$ref":"#/components/schemas/DnsRecord"}},"sent_count":{"type":"integer","description":"Messages sent from this domain"},"reputation":{"type":"integer","nullable":true,"description":"Computed reputation score (0-100), or null"},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp"},"verified_at":{"type":"string","format":"date-time","nullable":true,"description":"Verification time (ISO 8601), or null"},"shared_with_company":{"type":"boolean"},"shared_external":{"type":"boolean"},"owner_workspace_name":{"type":"string","nullable":true}},"required":["id","domain","status","spf","dkim","dmarc","region","dkim_selector","tracking_subdomain","dns_records","sent_count","reputation","created_at","verified_at"],"additionalProperties":false},"DomainListPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Domain"}},"total_count":{"type":"integer","description":"Total number of domains"}},"required":["items","total_count"],"additionalProperties":false},"EmailListEvaluation":{"type":"object","properties":{"total_count":{"type":"integer"},"unique_count":{"type":"integer"},"duplicate_count":{"type":"integer"},"syntax_errors":{"$ref":"#/components/schemas/EvaluationMetric"},"disposable":{"$ref":"#/components/schemas/EvaluationMetric"},"role_based":{"$ref":"#/components/schemas/EvaluationMetric"},"free_provider":{"$ref":"#/components/schemas/EvaluationMetric"},"typo_detected":{"$ref":"#/components/schemas/EvaluationMetric"},"quality_score":{"type":"integer","description":"Overall list quality score (0-100)"},"estimated_deliverable_pct":{"type":"number","description":"Estimated deliverable share of the list (percentage, may be fractional)"},"recommendation":{"type":"string","enum":["good","needs_cleaning","poor"]}},"required":["total_count","unique_count","duplicate_count","syntax_errors","disposable","role_based","free_provider","typo_detected","quality_score","estimated_deliverable_pct","recommendation"],"additionalProperties":false},"EmailQuota":{"type":"object","properties":{"limit":{"type":"number","nullable":true,"description":"Monthly email allowance; null means unlimited"},"used":{"type":"number","description":"Emails sent so far this period (test-mode sends excluded)"},"remaining":{"type":"number","nullable":true,"description":"Emails left before the plan limit; null when unlimited"},"ceiling":{"type":"number","nullable":true,"description":"Absolute stop including any overage policy headroom; null when unlimited. Sending pauses here, not at `limit`, when overage is enabled"},"overage_enabled":{"type":"boolean","description":"Whether the account may exceed `limit` into billed overage (Billing → Policy)"}},"required":["limit","used","remaining","ceiling","overage_enabled"],"additionalProperties":false,"description":"Monthly send quota — mirrors exactly what the send gate enforces"},"EngagementMetrics":{"type":"object","properties":{"opens":{"type":"integer","description":"Open events (bot and prefetch traffic is already filtered at ingest)"},"opens_direct":{"type":"integer","description":"Opens fetched directly by the recipient client (not via an image proxy)"},"unique_opens":{"type":"integer","description":"Distinct message+recipient pairs that opened"},"clicks":{"type":"integer"},"unique_clicks":{"type":"integer","description":"Distinct message+recipient pairs that clicked"},"unsubscribes":{"type":"integer"},"open_rate":{"type":"number","nullable":true,"description":"unique_opens / delivered"},"click_rate":{"type":"number","nullable":true,"description":"unique_clicks / delivered"},"unsubscribe_rate":{"type":"number","nullable":true,"description":"unsubscribes / delivered"}},"required":["opens","opens_direct","unique_opens","clicks","unique_clicks","unsubscribes","open_rate","click_rate","unsubscribe_rate"],"additionalProperties":false},"Error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable error code"},"message":{"type":"string","description":"Human-readable error message"}},"required":["code","message"],"additionalProperties":false},"ErrorResponse":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/Error"},"request_id":{"type":"string","description":"Unique request identifier"}},"required":["error","request_id"],"additionalProperties":false},"EvaluationMetric":{"type":"object","properties":{"count":{"type":"integer"},"percentage":{"type":"number"}},"required":["count","percentage"],"additionalProperties":false},"Event":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["queued","sent","delivered","opened","clicked","bounced","complained","unsubscribed","delayed","rejected","failed","duplicate"],"description":"Event type"},"message_id":{"type":"string"},"recipient":{"type":"string","nullable":true,"description":"Recipient email address the event applies to (null for message-level events)"},"timestamp":{"type":"string","format":"date-time","description":"When the event occurred (ISO 8601)"},"tags":{"type":"array","items":{"type":"string"}},"metadata":{"description":"Metadata attached at send time plus provider details, or null"},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp"}},"required":["id","type","message_id","recipient","timestamp","tags","created_at"],"additionalProperties":false},"EventExportAccepted":{"type":"object","properties":{"export_id":{"type":"string","description":"Poll GET /v3/events/export/{id}"},"status":{"type":"string","enum":["pending","processing"],"description":"pending for a new job; an equivalent already-running job is returned as-is"}},"required":["export_id","status"],"additionalProperties":false},"EventListPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"has_more":{"type":"boolean"},"next_cursor":{"type":"string","nullable":true},"total_count":{"type":"integer","description":"Total number of matching events"}},"required":["items","has_more","next_cursor","total_count"],"additionalProperties":false},"ExportJob":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","description":"pending | processing | completed | failed | expired"},"filename":{"type":"string"},"row_count":{"type":"integer","nullable":true,"description":"Rows in the file, set on completion"},"download_url":{"type":"string","nullable":true,"description":"Set once status is completed"},"error_message":{"type":"string","nullable":true,"description":"Set when the export failed (e.g. over the 1,000,000-row cap)"},"created_at":{"type":"string"},"completed_at":{"type":"string","nullable":true},"expires_at":{"type":"string","nullable":true,"description":"The file is deleted after this (7 days after completion)"}},"required":["id","status","filename","row_count","download_url","error_message","created_at","completed_at","expires_at"],"additionalProperties":false},"Identity":{"type":"object","properties":{"account":{"$ref":"#/components/schemas/AccountSummary"},"api_key":{"$ref":"#/components/schemas/ApiKeySummary"}},"required":["account","api_key"],"additionalProperties":false},"Ip":{"type":"object","properties":{"ip":{"type":"string","description":"IP address"},"pool_id":{"type":"string","format":"uuid","description":"Pool the IP belongs to"},"pool_name":{"type":"string","description":"Pool name"},"provider":{"type":"string","enum":["cloud-relay","apifon-postal"],"description":"Sending backend"},"region":{"type":"string","nullable":true,"description":"Provider region"}},"required":["ip","pool_id","pool_name","provider","region"],"additionalProperties":true,"description":"Dedicated IP"},"IpAllowlistEntry":{"type":"object","properties":{"cidr":{"type":"string","description":"Normalized IPv4/IPv6 CIDR"},"label":{"type":"string","description":"Human-readable label (may be empty)"}},"required":["cidr","label"],"additionalProperties":false},"IpListPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Ip"},"description":"Page of results"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["items","pagination"],"additionalProperties":false},"IpPool":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Pool ID"},"account_id":{"type":"string","nullable":true,"description":"Owning account ID (null = platform/shared pool, read-only)"},"name":{"type":"string","description":"Pool name"},"provider":{"type":"string","enum":["cloud-relay","apifon-postal"],"description":"Sending backend"},"region":{"type":"string","nullable":true,"description":"Provider region"},"type":{"type":"string","enum":["dedicated","shared","warming"],"description":"Pool type"},"ips":{"type":"array","items":{"type":"string"},"description":"IP addresses in the pool"},"reputation":{"type":"integer","nullable":true,"description":"Computed reputation score (staff-managed, read-only)"},"ip_details":{"description":"Per-IP metadata (staff-managed, read-only; null when absent)"},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp"}},"required":["id","account_id","name","provider","region","type","ips","reputation","created_at"],"additionalProperties":false,"description":"IP pool"},"IpPoolListPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/IpPool"},"description":"Page of results"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["items","pagination"],"additionalProperties":false},"LatencyBreakdown":{"type":"object","properties":{"processing":{"type":"object","properties":{"p50_ms":{"type":"integer","nullable":true},"p95_ms":{"type":"integer","nullable":true},"p99_ms":{"type":"integer","nullable":true}},"required":["p50_ms","p95_ms","p99_ms"],"additionalProperties":false,"description":"Accepted → handed to the outbound MTA"},"delivery":{"type":"object","properties":{"p50_ms":{"type":"integer","nullable":true},"p95_ms":{"type":"integer","nullable":true},"p99_ms":{"type":"integer","nullable":true}},"required":["p50_ms","p95_ms","p99_ms"],"additionalProperties":false,"description":"Handed to MTA → accepted by the recipient MX"},"total":{"type":"object","properties":{"p50_ms":{"type":"integer","nullable":true},"p95_ms":{"type":"integer","nullable":true},"p99_ms":{"type":"integer","nullable":true}},"required":["p50_ms","p95_ms","p99_ms"],"additionalProperties":false,"description":"Accepted → accepted by the recipient MX"}},"required":["processing","delivery","total"],"additionalProperties":false},"ListMember":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Member ID"},"list_id":{"type":"string","description":"Parent mailing list ID"},"address":{"type":"string","format":"email","description":"Member email address"},"name":{"type":"string","nullable":true,"description":"Optional display name"},"vars":{"description":"Free-form per-member merge variables (or null)"},"subscribed":{"type":"boolean","description":"Subscription state"},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp"}},"required":["id","list_id","address","name","subscribed","created_at"],"additionalProperties":false,"description":"Mailing list member"},"MailingList":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Mailing list ID"},"account_id":{"type":"string","description":"Owning account ID"},"address":{"type":"string","format":"email","description":"List email address (primary lookup key)"},"name":{"type":"string","description":"Display name"},"description":{"type":"string","nullable":true,"description":"Optional description"},"member_count":{"type":"integer","description":"Current member count"},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp"}},"required":["id","account_id","address","name","description","member_count","created_at"],"additionalProperties":false,"description":"Mailing list"},"MailingListMemberPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ListMember"},"description":"Page of results"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["items","pagination"],"additionalProperties":false},"MailingListPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/MailingList"},"description":"Page of results"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["items","pagination"],"additionalProperties":false},"Message":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["queued","sent","delivered","bounced","complained","deferred","rejected","failed"],"description":"Current delivery status"},"from":{"type":"string"},"to":{"type":"array","items":{"type":"string"}},"subject":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Sender-supplied custom X-* headers (e.g. correlation ids like X-TES-MSGID) — the same set stamped on the delivered email and echoed on every webhook"},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp"},"send_at":{"type":"string","format":"date-time","nullable":true,"description":"Scheduled delivery time (ISO 8601), or null"},"delivered_at":{"type":"string","format":"date-time","nullable":true,"description":"Delivery time (ISO 8601), or null"},"opened_at":{"type":"string","format":"date-time","nullable":true,"description":"First-open time (ISO 8601), or null"},"clicked_at":{"type":"string","format":"date-time","nullable":true,"description":"First-click time (ISO 8601), or null"}},"required":["id","status","from","to","subject","tags","headers","created_at","send_at","delivered_at","opened_at","clicked_at"],"additionalProperties":false},"MessageAccepted":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["queued"],"description":"Always `queued` — the message was accepted for delivery"},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp"}},"required":["id","status","created_at"],"additionalProperties":false},"MetricsBreakdown":{"type":"object","properties":{"data_as_of":{"type":"string","description":"Freshness stamp — metrics are computed live at request time (ISO 8601)"},"dimension":{"type":"string","enum":["sending_domain","recipient_domain","mailbox_provider","tag","template","bounce_code","complaint_type","country","client"]},"period":{"$ref":"#/components/schemas/MetricsPeriod"},"timezone":{"type":"string"},"sort":{"type":"string","enum":["sent","delivered","bounced","complained","opened","clicked","unique_opens","unique_clicks","failed"],"description":"Metric the rows are ranked by"},"total":{"type":"integer","description":"Distinct dimension values in the window (before limit)"},"data":{"type":"array","items":{"$ref":"#/components/schemas/MetricsBreakdownRow"}}},"required":["data_as_of","dimension","period","timezone","sort","total","data"],"additionalProperties":false},"MetricsBreakdownRow":{"type":"object","properties":{"key":{"type":"string","description":"Dimension value (domain, provider, tag, template ID, bounce class, country code, client family)"},"label":{"type":"string","nullable":true,"description":"Human-readable label where the key is an ID (template name, country name)"},"sent":{"type":"integer"},"delivered":{"type":"integer"},"bounced":{"type":"integer"},"complained":{"type":"integer"},"opened":{"type":"integer"},"clicked":{"type":"integer"},"unique_opens":{"type":"integer"},"unique_clicks":{"type":"integer"},"failed":{"type":"integer"},"delivery_rate":{"type":"number","nullable":true,"description":"0–1 fraction; null when the denominator is 0"},"bounce_rate":{"type":"number","nullable":true,"description":"0–1 fraction; null when the denominator is 0"},"complaint_rate":{"type":"number","nullable":true,"description":"0–1 fraction; null when the denominator is 0"},"open_rate":{"type":"number","nullable":true,"description":"0–1 fraction; null when the denominator is 0"},"click_rate":{"type":"number","nullable":true,"description":"0–1 fraction; null when the denominator is 0"},"trend":{"type":"array","items":{"$ref":"#/components/schemas/TrendPoint"},"description":"Per-day series of the sort metric; present when include_trend=true"}},"required":["key","label","sent","delivered","bounced","complained","opened","clicked","unique_opens","unique_clicks","failed","delivery_rate","bounce_rate","complaint_rate","open_rate","click_rate"],"additionalProperties":false},"MetricsComparison":{"type":"object","properties":{"period":{"$ref":"#/components/schemas/MetricsPeriod"},"delivery":{"$ref":"#/components/schemas/DeliveryMetrics"},"engagement":{"$ref":"#/components/schemas/EngagementMetrics"},"latency":{"$ref":"#/components/schemas/LatencyBreakdown"},"delta":{"$ref":"#/components/schemas/MetricsDelta"}},"required":["period","delivery","engagement","latency","delta"],"additionalProperties":false,"description":"Previous window of equal length plus deltas; present when compare=previous_period"},"MetricsDataPoint":{"type":"object","properties":{"timestamp":{"type":"string","format":"date-time","description":"Bucket start (ISO 8601, UTC instant of the local bucket boundary)"},"sent":{"type":"integer"},"delivered":{"type":"integer"},"bounced":{"type":"integer"},"complained":{"type":"integer"},"opened":{"type":"integer"},"clicked":{"type":"integer"},"unsubscribed":{"type":"integer"},"delayed":{"type":"integer","description":"Transient delivery delays (deferrals) reported by the receiving MX"},"rejected":{"type":"integer"},"failed":{"type":"integer"}},"required":["timestamp","sent","delivered","bounced","complained","opened","clicked","unsubscribed","delayed","rejected","failed"],"additionalProperties":false},"MetricsDelta":{"type":"object","properties":{"sent_pct_change":{"type":"number","nullable":true,"description":"Relative change vs the previous period (0.5 = +50%); null when the previous value is 0"},"delivered_pct_change":{"type":"number","nullable":true,"description":"Relative change vs the previous period (0.5 = +50%); null when the previous value is 0"},"bounced_pct_change":{"type":"number","nullable":true,"description":"Relative change vs the previous period (0.5 = +50%); null when the previous value is 0"},"complained_pct_change":{"type":"number","nullable":true,"description":"Relative change vs the previous period (0.5 = +50%); null when the previous value is 0"},"opens_pct_change":{"type":"number","nullable":true,"description":"Relative change vs the previous period (0.5 = +50%); null when the previous value is 0"},"clicks_pct_change":{"type":"number","nullable":true,"description":"Relative change vs the previous period (0.5 = +50%); null when the previous value is 0"},"delivery_rate_pp":{"type":"number","nullable":true,"description":"Percentage-point difference vs the previous period; null when either rate is null"},"bounce_rate_pp":{"type":"number","nullable":true,"description":"Percentage-point difference vs the previous period; null when either rate is null"},"complaint_rate_pp":{"type":"number","nullable":true,"description":"Percentage-point difference vs the previous period; null when either rate is null"},"open_rate_pp":{"type":"number","nullable":true,"description":"Percentage-point difference vs the previous period; null when either rate is null"},"click_rate_pp":{"type":"number","nullable":true,"description":"Percentage-point difference vs the previous period; null when either rate is null"},"unsubscribe_rate_pp":{"type":"number","nullable":true,"description":"Percentage-point difference vs the previous period; null when either rate is null"}},"required":["sent_pct_change","delivered_pct_change","bounced_pct_change","complained_pct_change","opens_pct_change","clicks_pct_change","delivery_rate_pp","bounce_rate_pp","complaint_rate_pp","open_rate_pp","click_rate_pp","unsubscribe_rate_pp"],"additionalProperties":false},"MetricsGroup":{"type":"object","properties":{"key":{"type":"string","description":"Sending domain (group_by=domain) or event type (group_by=status)"},"data":{"type":"array","items":{"$ref":"#/components/schemas/MetricsDataPoint"},"description":"Sparse per-group series (idle buckets omitted)"}},"required":["key","data"],"additionalProperties":false},"MetricsPeriod":{"type":"object","properties":{"since":{"type":"string","format":"date-time","description":"Period start (ISO 8601)"},"until":{"type":"string","format":"date-time","description":"Period end (ISO 8601)"}},"required":["since","until"],"additionalProperties":false},"MetricsSummary":{"type":"object","properties":{"data_as_of":{"type":"string","description":"Freshness stamp — metrics are computed live at request time (ISO 8601)"},"period":{"$ref":"#/components/schemas/MetricsPeriod"},"delivery":{"$ref":"#/components/schemas/DeliveryMetrics"},"engagement":{"$ref":"#/components/schemas/EngagementMetrics"},"latency":{"$ref":"#/components/schemas/LatencyBreakdown"},"timezone":{"type":"string"},"comparison":{"$ref":"#/components/schemas/MetricsComparison"}},"required":["data_as_of","period","delivery","engagement","latency","timezone"],"additionalProperties":false},"MetricsTimeseries":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MetricsDataPoint"},"description":"Gap-filled series: every bucket in the window is present, zeroed when idle"},"resolution":{"type":"string","enum":["hour","day","week","month"]},"data_as_of":{"type":"string","description":"Freshness stamp — metrics are computed live at request time (ISO 8601)"},"timezone":{"type":"string","description":"IANA timezone the buckets were computed in"},"period":{"$ref":"#/components/schemas/MetricsPeriod"},"group_by":{"type":"string","enum":["domain","status",null],"nullable":true},"groups":{"type":"array","items":{"$ref":"#/components/schemas/MetricsGroup"},"description":"Per-group series, present when group_by is set"}},"required":["data","resolution","data_as_of","timezone","period","group_by"],"additionalProperties":false},"Pagination":{"type":"object","properties":{"page":{"type":"integer","description":"Current page (1-based)"},"per_page":{"type":"integer","description":"Items per page"},"total_count":{"type":"integer","description":"Total number of items across all pages"},"total_pages":{"type":"integer","description":"Total number of pages"}},"required":["page","per_page","total_count","total_pages"],"additionalProperties":false},"ProofVerificationResult":{"type":"object","properties":{"verified":{"type":"boolean"},"signature":{"type":"string","enum":["valid","invalid","unverified"]},"fingerprint":{"type":"string","enum":["matches","mismatch","unknown"]},"registry":{"type":"string"},"issued":{"type":"string","format":"date-time"}},"required":["verified","registry"],"additionalProperties":false},"ScheduledMessage":{"type":"object","properties":{"id":{"type":"string","description":"Message id (msg_…) — use it with GET /v3/messages/{id} and DELETE /v3/messages/{id}"},"from":{"type":"string"},"to":{"type":"array","items":{"type":"string"}},"subject":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"send_at":{"type":"string","format":"date-time","description":"Scheduled delivery time (ISO 8601, UTC)"},"created_at":{"type":"string","format":"date-time","description":"When the send was accepted (ISO 8601)"}},"required":["id","from","to","subject","tags","send_at","created_at"],"additionalProperties":false},"ScheduledMessageCancellation":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["cancelled"],"description":"Always `cancelled` — the scheduled send was cancelled"},"message":{"type":"string"}},"required":["id","status","message"],"additionalProperties":false},"ScheduledMessageListPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/ScheduledMessage"},"description":"Pending scheduled sends, soonest delivery first"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["items","pagination"],"additionalProperties":false},"Subaccount":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Subaccount ID"},"parent_account_id":{"type":"string","description":"Parent account ID"},"name":{"type":"string","description":"Display name"},"slug":{"type":"string","description":"URL-safe identifier (immutable after create)"},"status":{"type":"string","enum":["active","suspended"],"description":"Subaccount status"},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp"}},"required":["id","parent_account_id","name","slug","status","created_at"],"additionalProperties":false,"description":"Subaccount"},"SubaccountListPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Subaccount"},"description":"Page of results"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["items","pagination"],"additionalProperties":false},"Suppression":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Suppression entry ID"},"account_id":{"type":"string","description":"Owning account ID"},"type":{"type":"string","enum":["bounces","unsubscribes","complaints","allowlist"],"description":"Suppression list the entry belongs to"},"email":{"type":"string","description":"Suppressed email address (stored lowercased)"},"reason":{"type":"string","nullable":true,"description":"Optional reason"},"source":{"type":"string","enum":["verification","manual","import","delivery_report",null],"nullable":true,"description":"How the entry was added"},"override_unsubscribes":{"type":"boolean","description":"Allowlist entries only: true when this allowlist entry also overrides an unsubscribe for the address. Always false on other types."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp"}},"required":["id","account_id","type","email","reason","source","override_unsubscribes","created_at"],"additionalProperties":false,"description":"Suppression entry"},"SuppressionExportAccepted":{"type":"object","properties":{"export_id":{"type":"string","description":"Poll GET /v3/suppressions/exports/{id}"},"status":{"type":"string","enum":["pending"]}},"required":["export_id","status"],"additionalProperties":false},"SuppressionImport":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"status":{"type":"string","enum":["pending","processing","done","failed"]},"filename":{"type":"string"},"total_rows":{"type":"integer","nullable":true},"processed_rows":{"type":"integer"},"added":{"type":"integer"},"skipped":{"type":"integer"},"errors":{"type":"array","items":{"$ref":"#/components/schemas/SuppressionImportError"},"nullable":true,"description":"First 500 per-row errors (1-based row numbers, header included)"},"error_message":{"type":"string","nullable":true},"created_at":{"type":"string"},"completed_at":{"type":"string","nullable":true}},"required":["id","type","status","filename","total_rows","processed_rows","added","skipped","errors","error_message","created_at","completed_at"],"additionalProperties":false},"SuppressionImportAccepted":{"type":"object","properties":{"import_id":{"type":"string","description":"Poll GET /v3/suppressions/imports/{id}"},"status":{"type":"string","enum":["pending"]}},"required":["import_id","status"],"additionalProperties":false},"SuppressionImportError":{"type":"object","properties":{"row":{"type":"integer"},"email":{"type":"string"},"reason":{"type":"string"}},"required":["row","email","reason"],"additionalProperties":false},"SuppressionListPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Suppression"},"description":"Page of results"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["items","pagination"],"additionalProperties":false},"Template":{"type":"object","properties":{"id":{"type":"string","description":"Template ID (tpl_…)"},"name":{"type":"string","description":"Template name"},"description":{"type":"string","nullable":true,"description":"Optional description"},"subject":{"type":"string","nullable":true,"description":"Subject line (may contain {{variables}})"},"html":{"type":"string","nullable":true,"description":"HTML body (may contain {{variables}})"},"text_body":{"type":"string","nullable":true,"description":"Plain-text body"},"vars_schema":{"description":"JSON schema describing the template variables (null when unset)"},"version":{"type":"integer","description":"Template version, incremented on update"},"active":{"type":"boolean","description":"Whether the template can be used for sending"},"category":{"type":"string","nullable":true,"description":"Optional grouping category"},"created_at":{"type":"string","format":"date-time","nullable":true,"description":"ISO 8601 creation timestamp"},"updated_at":{"type":"string","format":"date-time","nullable":true,"description":"ISO 8601 last-update timestamp"}},"required":["id","name","description","subject","html","text_body","version","active","category","created_at","updated_at"],"additionalProperties":false},"TemplateListPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Template"},"description":"Page of templates"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["items","pagination"],"additionalProperties":false},"TrendPoint":{"type":"object","properties":{"timestamp":{"type":"string","format":"date-time","description":"Day bucket start (ISO 8601)"},"value":{"type":"integer","description":"Value of the sort metric in that day"}},"required":["timestamp","value"],"additionalProperties":false},"Upload":{"type":"object","properties":{"id":{"type":"string","description":"Upload id (up_…)"},"filename":{"type":"string","description":"Filename recipients see and download"},"content_type":{"type":"string","description":"MIME type of the file"},"size_bytes":{"type":"integer","description":"File size in bytes"},"url":{"type":"string","format":"uri","nullable":true,"description":"Stable hosted download URL served from YOUR branded tracking host (track.yourdomain.com) — put this on a link or button in your email HTML; clicks are wrapped and tracked like any other link. Null while the account has no verified domain with the branded tracking CNAME active (e.g. it was removed after upload)."},"download_count":{"type":"integer","description":"Number of times the file has been downloaded (human downloads only — mailbox scanners are excluded)"},"expires_at":{"type":"string","format":"date-time","nullable":true,"description":"ISO 8601 expiry, or null if the upload never expires. Expired uploads answer 410 Gone."},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 creation timestamp"}},"required":["id","filename","content_type","size_bytes","url","download_count","expires_at","created_at"],"additionalProperties":false},"UploadDownloadLink":{"type":"object","properties":{"url":{"type":"string","format":"uri","nullable":true,"description":"Short-lived download URL that streams the file WITHOUT incrementing its download count. In production this is a SAS URL served straight from blob storage; in local dev (disk driver) it falls back to the public /f/ URL."}},"required":["url"],"additionalProperties":false},"UploadListPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Upload"}},"used_bytes":{"type":"integer","description":"Total bytes currently counting toward your storage quota"},"quota_bytes":{"type":"integer","description":"Your storage quota in bytes. Hosted uploads are available on paid plans only (Pay-as-you-go counts as paid): 100 MB included, raised to 10 GB or 100 GB by a storage add-on. Free plans have no upload storage (0)."}},"required":["items","used_bytes","quota_bytes"],"additionalProperties":false},"Usage":{"type":"object","properties":{"plan":{"type":"string","description":"The workspace plan (`free` or `professional`)"},"quota_source":{"type":"string","enum":["own_plan","shared_pool","payg_credits"],"description":"Where the monthly allowance comes from: the workspace's own plan, the organisation's shared pool allocation, or a prepaid pay-as-you-go credit block"},"period":{"$ref":"#/components/schemas/BillingPeriod"},"emails":{"$ref":"#/components/schemas/EmailQuota"},"daily":{"$ref":"#/components/schemas/DailyQuota"},"verify_credits":{"$ref":"#/components/schemas/VerifyCredits"},"rate_limit_per_minute":{"type":"number","nullable":true,"description":"Account-level API rate limit; null means the platform default"}},"required":["plan","quota_source","period","emails","daily","verify_credits","rate_limit_per_minute"],"additionalProperties":false},"ValidationResult":{"type":"object","properties":{"verification_id":{"type":"string","nullable":true,"description":"id of the stored verification record (null if the history write failed)"},"address":{"type":"string","description":"The address exactly as submitted for validation"},"is_valid":{"type":"boolean"},"result":{"type":"string","enum":["valid","invalid","catch_all","unknown","spamtrap","abuse","do_not_mail"]},"status":{"type":"string","enum":["valid","invalid","catch_all","unknown","spamtrap","abuse","do_not_mail"]},"sub_status":{"type":"string"},"recommendation":{"type":"string","enum":["mail","caution","suppress","review"]},"confidence":{"type":"number"},"risk":{"type":"string","enum":["low","medium","high","undeliverable"]},"reason":{"type":"string"},"did_you_mean":{"type":"string","nullable":true},"account":{"type":"string"},"domain":{"type":"string"},"mx_provider":{"type":"string","nullable":true},"mx_record":{"type":"string","nullable":true},"mx_found":{"type":"boolean"},"is_catch_all":{"type":"boolean"},"is_disposable":{"type":"boolean"},"is_role_address":{"type":"boolean"},"is_free_provider":{"type":"boolean"},"smtp_check":{"type":"boolean","nullable":true},"domain_age_days":{"type":"integer","nullable":true},"activity":{"type":"object","properties":{"seen":{"type":"boolean"},"first_seen_at":{"type":"string","format":"date-time","nullable":true,"description":"First send to this address (ISO 8601), or null"},"last_seen_at":{"type":"string","format":"date-time","nullable":true,"description":"Most recent send to this address (ISO 8601), or null"},"total_sends":{"type":"integer"},"delivered":{"type":"integer"},"bounced":{"type":"integer"},"opened":{"type":"integer"},"clicked":{"type":"integer"},"complained":{"type":"integer"},"last_event_type":{"type":"string","nullable":true},"last_event_at":{"type":"string","format":"date-time","nullable":true,"description":"Most recent event time (ISO 8601), or null"}},"required":["seen","first_seen_at","last_seen_at","total_sends","delivered","bounced","opened","clicked","complained","last_event_type","last_event_at"],"additionalProperties":false,"nullable":true}},"required":["address","is_valid","result","sub_status","recommendation","confidence","risk","reason","did_you_mean","account","domain","mx_provider","mx_record","mx_found","is_catch_all","is_disposable","is_role_address","is_free_provider","smtp_check","domain_age_days"],"additionalProperties":false},"VerificationRule":{"type":"object","properties":{"id":{"type":"string"},"rule_type":{"type":"string","enum":["email","domain","tld","mx_provider"]},"value":{"type":"string"},"action":{"type":"string","enum":["allow","block"]},"priority":{"type":"integer"},"reason":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp"}},"required":["id","rule_type","value","action","priority","reason","created_at"],"additionalProperties":false},"VerificationRuleList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/VerificationRule"}}},"required":["items"],"additionalProperties":false},"VerifyCredits":{"type":"object","properties":{"balance":{"type":"number","description":"Prepaid email-verification credits available"}},"required":["balance"],"additionalProperties":false,"description":"Verification credit balance — verification endpoints return 402 when this runs out"},"Webhook":{"type":"object","properties":{"id":{"type":"string","description":"Webhook endpoint ID"},"url":{"type":"string","format":"uri","description":"Destination URL that receives event POSTs"},"events":{"type":"array","items":{"type":"string","enum":["queued","sent","delivered","opened","clicked","bounced","complained","unsubscribed","delayed","rejected","failed"]},"description":"Subscribed event types (canonical): queued | sent | delivered | opened | clicked | bounced | complained | unsubscribed | delayed | rejected | failed"},"enabled":{"type":"boolean","description":"Whether deliveries are active"},"status":{"type":"string","enum":["active","disabled"],"description":"Delivery status derived from `enabled`: active = delivering, disabled = paused (see `disabled_reason`)"},"disabled_reason":{"type":"string","enum":["manual","auto_failures",null],"nullable":true,"description":"Why the endpoint is disabled: `manual` = paused by a user (`enabled: false`), `auto_failures` = auto-disabled after 20 consecutive failed deliveries spanning 72+ hours. `null` while active. Re-enabling (`enabled: true`) clears it."},"disabled_at":{"type":"string","format":"date-time","nullable":true,"description":"ISO 8601 timestamp of when the endpoint was disabled (null while active)"},"domain_id":{"type":"string","nullable":true,"description":"Scoping domain ID (null = events for all domains)"},"domain":{"type":"string","nullable":true,"description":"Scoping domain name for display (null = all domains)"},"auth":{"$ref":"#/components/schemas/WebhookAuth"},"created_at":{"type":"string","format":"date-time","nullable":true,"description":"ISO 8601 creation timestamp"},"updated_at":{"type":"string","format":"date-time","nullable":true,"description":"ISO 8601 last-update timestamp"}},"required":["id","url","events","enabled","status","disabled_reason","disabled_at","domain_id","domain","created_at","updated_at"],"additionalProperties":false},"WebhookAuth":{"type":"object","properties":{"type":{"type":"string","enum":["none","bearer","basic","custom_headers","oauth2","hmac","mtls"],"description":"Configured receiver-side auth scheme (\"none\" when unset)"},"token_set":{"type":"boolean","description":"True when a bearer token is stored (the token itself is never returned)"},"username":{"type":"string","description":"HTTP Basic username"},"password_set":{"type":"boolean","description":"True when a Basic password is stored (never returned)"},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Custom header names — values are masked (\"•••••\")"},"token_url":{"type":"string","format":"uri","description":"OAuth 2.0 token endpoint"},"client_id":{"type":"string","description":"OAuth 2.0 client id"},"client_secret_set":{"type":"boolean","description":"True when an OAuth 2.0 client secret is stored (never returned)"},"scope":{"type":"string","description":"OAuth 2.0 scope"},"audience":{"type":"string","description":"OAuth 2.0 audience"},"secret":{"type":"string","description":"HMAC signing secret in plaintext — present ONLY in the create/update response that generated or accepted it; never returned again"},"secret_set":{"type":"boolean","description":"True when an HMAC signing secret is stored (never returned after the one-time reveal)"},"secret_hint":{"type":"string","description":"Last 4 characters of the stored HMAC secret, prefixed with \"…\""},"header_name":{"type":"string","description":"Header carrying the HMAC signature (default \"X-Wemail-Signature\")"},"rotated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of the last HMAC secret rotation"},"previous_secret_valid_until":{"type":"string","format":"date-time","description":"ISO 8601 end of the 24h rotation window during which the previous HMAC secret also signs deliveries (present only while the window is open)"},"cert_set":{"type":"boolean","description":"True when an mTLS client certificate is stored"},"key_set":{"type":"boolean","description":"True when an mTLS client private key is stored (never returned)"},"cert_fingerprint_sha256":{"type":"string","description":"SHA-256 fingerprint of the mTLS client certificate (colon-separated hex)"},"cert_subject":{"type":"string","description":"Subject CN of the mTLS client certificate"},"cert_expires_at":{"type":"string","format":"date-time","description":"ISO 8601 expiry (notAfter) of the mTLS client certificate"}},"required":["type"],"additionalProperties":false,"description":"Receiver-side authentication (redacted — secrets are echoed as *_set booleans)"},"WebhookDelivery":{"type":"object","properties":{"webhook_id":{"type":"string","format":"uuid","description":"The endpoint the delivery belongs to — pass to resend as-is"},"event_id":{"type":"string","description":"The source event id — pass to resend as-is"},"event":{"type":"string","description":"Source event type (e.g. delivered, bounced)"},"attempts":{"type":"integer","description":"Attempts made so far (organic retries + manual resends)"},"last_status":{"type":"integer","nullable":true,"description":"HTTP status of the latest attempt (null = network error, timeout or in-flight)"},"last_attempt_at":{"type":"string","format":"date-time","description":"When the latest attempt was made (ISO 8601)"},"state":{"type":"string","enum":["delivered","failed","pending","retrying"],"description":"Derived delivery state: delivered = 2xx received; retrying = failed but the automatic schedule continues; failed = final (resendable); pending = a manual resend is in flight"}},"required":["webhook_id","event_id","event","attempts","last_status","last_attempt_at","state"],"additionalProperties":false},"WebhookDeliveryListPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"},"description":"Logical deliveries, newest first"},"has_more":{"type":"boolean","description":"True when another page exists"},"next_cursor":{"type":"string","nullable":true,"description":"Opaque cursor for the next page (null on the last page)"},"total_count":{"type":"integer","description":"Total matching deliveries in the window (after the status filter)"}},"required":["items","has_more","next_cursor","total_count"],"additionalProperties":false},"WebhookListPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"},"description":"Page of results"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["items","pagination"],"additionalProperties":false},"WebhookResendOutcome":{"type":"object","properties":{"webhook_id":{"type":"string","description":"Source endpoint id"},"event_id":{"type":"string","description":"Event id"},"accepted":{"type":"boolean","description":"True when a background resend was queued"},"reason":{"type":"string","enum":["locked","not_failed","not_found",null],"nullable":true,"description":"Why the delivery was skipped: locked = duplicate submission within 60 s; not_failed = latest attempt is not failed; not_found = no such delivery in the last 90 days"}},"required":["webhook_id","event_id","accepted","reason"],"additionalProperties":false},"WebhookResendResult":{"type":"object","properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/WebhookResendOutcome"}},"accepted_count":{"type":"integer","description":"Deliveries queued for resend"},"skipped_count":{"type":"integer","description":"Deliveries skipped (see per-item reason)"},"matched_count":{"type":"integer","description":"Failed deliveries the query matched (pairs mode: the submitted count)"},"has_more":{"type":"boolean","description":"Query mode: more matches exist beyond limit — repeat the same call to drain them"}},"required":["results","accepted_count","skipped_count","matched_count","has_more"],"additionalProperties":false},"WebhookTestDelivery":{"type":"object","properties":{"event":{"type":"string","description":"The event type that was fired"},"status":{"type":"integer","nullable":true,"description":"HTTP status your endpoint returned (null when the request never completed — timeout or network failure)"},"latency_ms":{"type":"integer","description":"Round-trip time of the attempt in milliseconds"},"success":{"type":"boolean","description":"True when the endpoint answered 2xx within the timeout"}},"required":["event","status","latency_ms","success"],"additionalProperties":false},"WebhookTestResult":{"type":"object","properties":{"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/WebhookTestDelivery"},"description":"One entry per fired event (an array for future multi-event test fires)"}},"required":["deliveries"],"additionalProperties":false},"WebhookWithSecret":{"type":"object","properties":{"id":{"type":"string","description":"Webhook endpoint ID"},"url":{"type":"string","format":"uri","description":"Destination URL that receives event POSTs"},"events":{"type":"array","items":{"type":"string","enum":["queued","sent","delivered","opened","clicked","bounced","complained","unsubscribed","delayed","rejected","failed"]},"description":"Subscribed event types (canonical): queued | sent | delivered | opened | clicked | bounced | complained | unsubscribed | delayed | rejected | failed"},"enabled":{"type":"boolean","description":"Whether deliveries are active"},"status":{"type":"string","enum":["active","disabled"],"description":"Delivery status derived from `enabled`: active = delivering, disabled = paused (see `disabled_reason`)"},"disabled_reason":{"type":"string","enum":["manual","auto_failures",null],"nullable":true,"description":"Why the endpoint is disabled: `manual` = paused by a user (`enabled: false`), `auto_failures` = auto-disabled after 20 consecutive failed deliveries spanning 72+ hours. `null` while active. Re-enabling (`enabled: true`) clears it."},"disabled_at":{"type":"string","format":"date-time","nullable":true,"description":"ISO 8601 timestamp of when the endpoint was disabled (null while active)"},"domain_id":{"type":"string","nullable":true,"description":"Scoping domain ID (null = events for all domains)"},"domain":{"type":"string","nullable":true,"description":"Scoping domain name for display (null = all domains)"},"auth":{"$ref":"#/components/schemas/WebhookAuth"},"created_at":{"type":"string","format":"date-time","nullable":true,"description":"ISO 8601 creation timestamp"},"updated_at":{"type":"string","format":"date-time","nullable":true,"description":"ISO 8601 last-update timestamp"},"secret":{"type":"string","description":"HMAC signing secret (whsec_…) — returned ONLY at creation time. Used to verify signed deliveries; see https://wemail.io/docs/webhooks"}},"required":["id","url","events","enabled","status","disabled_reason","disabled_at","domain_id","domain","created_at","updated_at","secret"],"additionalProperties":false}},"responses":{"RateLimited":{"description":"Rate limit exceeded — retry after the indicated delay.","headers":{"x-ratelimit-limit":{"description":"Request limit for the current window.","schema":{"type":"integer"}},"x-ratelimit-remaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"x-ratelimit-reset":{"description":"Seconds until the current window resets.","schema":{"type":"integer"}},"retry-after":{"description":"Seconds to wait before retrying the request.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"InternalError":{"description":"Unexpected server error — the standard error envelope with a request_id to quote to support.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"paths":{"/messages":{"post":{"operationId":"sendMessage","summary":"Send an email message","tags":["Messages"],"description":"Send a single transactional email to one or more recipients. Custom X-* headers (e.g. X-TES-MSGID) supplied in `headers` are stamped on the delivered email, echoed on every webhook for the message, and returned by GET /v3/messages/{id} — use them to correlate wemail events with your own system.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"string","maxLength":400,"description":"Sender address — `email@domain` or `Display Name <email@domain>` (domain must be verified)"},"from_name":{"type":"string","minLength":1,"maxLength":256,"description":"Display name for the From header (`Display Name <from>`). Overrides a name embedded in `from`."},"to":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":1000,"description":"Recipient email address or array of addresses"},"cc":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":1000,"description":"CC recipients"},"bcc":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":1000,"description":"BCC recipients"},"reply_to":{"type":"string","description":"Reply-to email address"},"subject":{"type":"string","minLength":1,"maxLength":998,"description":"Email subject line"},"html":{"type":"string","description":"HTML body content"},"text":{"type":"string","description":"Plain text body content"},"template_id":{"type":"string","pattern":"^tpl_[A-Za-z0-9]+$","description":"Stored template id (tpl_…) to render. Its subject/body take precedence over inline subject/html/text; vars are substituted into it."},"vars":{"type":"object","additionalProperties":{},"description":"Template variables"},"tags":{"type":"array","items":{"type":"string","maxLength":128},"maxItems":10,"description":"Tags for categorization (max 10)"},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Custom email headers. X-* headers (e.g. a correlation id like X-TES-MSGID) are stamped on the delivered email, stored with the message, echoed on every webhook payload for the message, and returned by GET /v3/messages/{id}. Limits: 32 headers, 1KB per value; X-Wemail-*/X-SES-* names are reserved."},"attachments":{"type":"array","items":{"type":"object","properties":{"filename":{"type":"string"},"content":{"type":"string","maxLength":7100000},"content_type":{"type":"string"},"content_id":{"type":"string","maxLength":256,"description":"Content-ID for the attachment (without angle brackets). Reference it from HTML as `<img src=\"cid:VALUE\">` to embed the image inline. Setting this emits a `Content-ID` header and builds a `multipart/related` message."},"disposition":{"type":"string","enum":["inline","attachment"],"description":"How the client should present the attachment. `inline` (typically paired with `content_id`) embeds it in the body; `attachment` (the default) offers it as a downloadable file."}},"required":["filename","content"],"additionalProperties":false},"description":"File attachments (base64 encoded). Limits: 5 MB decoded per file, 20 MB decoded per message (all files combined) and 30 GB total per send (size × recipients — every recipient receives a full copy). Host larger files via POST /v3/uploads."},"tracking":{"type":"object","properties":{"opens":{"type":"boolean"},"clicks":{"type":"boolean"},"https":{"type":"boolean"}},"additionalProperties":false,"description":"Tracking options for opens and clicks"},"send_at":{"type":"string","format":"date-time","description":"Schedule delivery at this ISO 8601 timestamp (must be in the future, at most one year out)"},"metadata":{"type":"object","additionalProperties":{},"description":"Custom metadata attached to the message"},"verify":{"type":"boolean","description":"Verify each recipient before sending. Overrides the account/domain/API-key setting for this message."},"webhook_url":{"type":"string","format":"uri","maxLength":2048,"description":"Per-message webhook URL. Every event for this message (queued, delivered, opened, …) is ALSO posted to this HTTPS URL, in addition to the account's configured webhook endpoints. Payloads are signed with the Wemail-Signature-Ed25519 header."}},"required":["from","to","subject"],"additionalProperties":false},"example":{"from":"Acme <hello@acme.io>","to":["john@example.com"],"subject":"Welcome to Acme","html":"<h1>Welcome!</h1><p>Thanks for signing up.</p>","text":"Welcome! Thanks for signing up.","tags":["onboarding"],"headers":{"X-TES-MSGID":"order-10021"}}}}},"security":[{"bearerAuth":[]}],"responses":{"202":{"description":"Message accepted for delivery","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageAccepted"},"example":{"id":"msg_a1b2c3d4e5f64789","status":"queued","created_at":"2026-08-16T09:30:00.000Z"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden (sandbox recipient not on the confirmed allow-list, or account suspended)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"A request with this idempotency key is already being processed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"Sending temporarily paused for this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional unique key to safely retry the request without sending duplicate emails. For 24 hours, a replay with the same key returns the original response instead of sending again; a concurrent replay while the first request is still processing returns 409.","schema":{"type":"string","maxLength":255}}]}},"/messages.batch":{"post":{"operationId":"sendBatchMessages","summary":"Send a batch of email messages","tags":["Messages"],"description":"Send a batch of personalized emails to multiple recipients in a single request. Custom X-* headers supplied in `headers` apply to every message in the batch: stamped on the delivered emails, echoed on webhooks, and returned by GET /v3/messages/{id}.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"string","maxLength":400,"description":"Sender address — `email@domain` or `Display Name <email@domain>` (domain must be verified)"},"from_name":{"type":"string","minLength":1,"maxLength":256,"description":"Display name for the From header (`Display Name <from>`). Overrides a name embedded in `from`."},"subject":{"type":"string","minLength":1,"maxLength":998},"html":{"type":"string"},"text":{"type":"string"},"template_id":{"type":"string","pattern":"^tpl_[A-Za-z0-9]+$"},"recipients":{"type":"array","items":{"type":"object","properties":{"to":{"type":"array","items":{"type":"string","minLength":1,"maxLength":320},"minItems":1,"maxItems":1000,"description":"Recipient address(es). Invalid addresses reject only that recipient — see rejected_recipients in the response."},"name":{"type":"string"},"vars":{"type":"object","additionalProperties":{}},"tags":{"type":"array","items":{"type":"string","maxLength":128},"maxItems":10},"metadata":{"type":"object","additionalProperties":{}},"send_at":{"type":"string","format":"date-time"}},"required":["to"],"additionalProperties":false},"minItems":1,"maxItems":5000},"global_vars":{"type":"object","additionalProperties":{}},"tags":{"type":"array","items":{"type":"string","maxLength":128},"maxItems":10},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Custom email headers applied to every message in the batch. X-* headers (e.g. a correlation id like X-TES-MSGID) are stamped on the delivered emails, stored with each message, echoed on every webhook payload, and returned by GET /v3/messages/{id}. Limits: 32 headers, 1KB per value; X-Wemail-*/X-SES-* names are reserved."},"attachments":{"type":"array","items":{"type":"object","properties":{"filename":{"type":"string"},"content":{"type":"string","maxLength":7100000},"content_type":{"type":"string"},"content_id":{"type":"string","maxLength":256,"description":"Content-ID for the attachment (without angle brackets). Reference it from HTML as `<img src=\"cid:VALUE\">` to embed the image inline. Setting this emits a `Content-ID` header and builds a `multipart/related` message."},"disposition":{"type":"string","enum":["inline","attachment"],"description":"How the client should present the attachment. `inline` (typically paired with `content_id`) embeds it in the body; `attachment` (the default) offers it as a downloadable file."}},"required":["filename","content"],"additionalProperties":false},"description":"File attachments (base64 encoded) applied to every message in the batch. Limits: 5 MB decoded per file, 20 MB decoded per message (all files combined) and 30 GB total per send (size × total recipients — every recipient receives a full copy). Host larger files via POST /v3/uploads."},"tracking":{"type":"object","properties":{"opens":{"type":"boolean"},"clicks":{"type":"boolean"},"https":{"type":"boolean"}},"additionalProperties":false},"webhook_url":{"type":"string","format":"uri","maxLength":2048,"description":"Per-message webhook URL applied to every message in the batch. Every event is ALSO posted to this HTTPS URL, in addition to the account's configured webhook endpoints. Payloads are signed with the Wemail-Signature-Ed25519 header."}},"required":["from","subject","recipients"],"additionalProperties":false},"example":{"from":"Acme <hello@acme.io>","subject":"Your weekly digest","html":"<h1>Hi {{first_name}}</h1><p>Here is your digest.</p>","recipients":[{"to":["john@example.com"],"vars":{"first_name":"John"}},{"to":["maria@example.com"],"vars":{"first_name":"Maria"}}],"tags":["digest"]}}}},"security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Batch accepted for delivery","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchSendResult"},"example":{"batch_id":"batch_7f3a2b1c9d8e4f50","accepted":2,"rejected":0,"message_ids":["msg_a1b2c3d4e5f64789","msg_b2c3d4e5f6a74890"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden (sandbox recipient not on the confirmed allow-list, or account suspended)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"A request with this idempotency key is already being processed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional unique key to safely retry the request without sending duplicate emails. For 24 hours, a replay with the same key returns the original response instead of sending again; a concurrent replay while the first request is still processing returns 409.","schema":{"type":"string","maxLength":255}}]}},"/messages/scheduled":{"get":{"operationId":"listScheduledMessages","summary":"List scheduled messages","tags":["Messages"],"description":"List sends scheduled with `send_at` that have not been delivered or cancelled yet — soonest delivery first. Each item carries the `msg_…` id to inspect (`GET /v3/messages/{id}`) or cancel (`DELETE /v3/messages/{id}`) the send.","parameters":[{"schema":{"type":"integer","minimum":1,"default":1},"in":"query","name":"page","required":false,"description":"Page number (1-based)"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"in":"query","name":"per_page","required":false,"description":"Items per page (max 100)"}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Pending scheduled sends","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduledMessageListPage"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/messages/{id}":{"get":{"operationId":"getMessage","summary":"Get a message","tags":["Messages"],"description":"Fetch a single message and its current delivery state by id. Includes the sender-supplied custom X-* headers (correlation ids) that were stamped on the delivered email.","parameters":[{"schema":{"type":"string","pattern":"^msg\\_"},"in":"path","name":"id","required":true,"description":"Message ID (starts with msg_)"}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Message detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"},"example":{"id":"msg_a1b2c3d4e5f64789","status":"delivered","from":"hello@acme.io","to":["john@example.com"],"subject":"Welcome to Acme","tags":["onboarding"],"headers":{"X-TES-MSGID":"order-10021"},"created_at":"2026-08-16T09:30:00.000Z","send_at":null,"delivered_at":"2026-08-16T09:30:04.000Z","opened_at":"2026-08-16T10:02:11.000Z","clicked_at":null}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Message not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"delete":{"operationId":"cancelScheduledMessage","summary":"Cancel a scheduled message","tags":["Messages"],"description":"Cancel a message that was scheduled for future delivery.","parameters":[{"schema":{"type":"string","pattern":"^msg\\_"},"in":"path","name":"id","required":true,"description":"Message ID (starts with msg_)"}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Message cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduledMessageCancellation"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Message not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/events":{"get":{"operationId":"listEvents","summary":"List email events","tags":["Events"],"description":"Retrieve a paginated list of email events with optional filters. Supports METADATA SEARCH: `?metadata[order_id]=1234` matches events whose message (or the event itself) carries that metadata key/value — the metadata you attach at send time. Up to 3 metadata filters per request (keys up to 64 chars of letters/digits/dot/dash/underscore, values up to 200 chars, exact match); they combine with every other filter, and pagination cursors remain stable. Answer “what happened to order 1234’s email?” in one call.","parameters":[{"schema":{"type":"string","enum":["queued","sent","delivered","opened","clicked","bounced","complained","unsubscribed","delayed","rejected","failed","duplicate"]},"in":"query","name":"event","required":false,"description":"Filter by event type (e.g. delivered, bounced)"},{"schema":{"type":"string"},"in":"query","name":"recipient","required":false,"description":"Filter by recipient email address"},{"schema":{"type":"string"},"in":"query","name":"tag","required":false,"description":"Filter by message tag"},{"schema":{"type":"string"},"in":"query","name":"message_id","required":false,"description":"Filter by message ID"},{"schema":{"type":"string","format":"date-time"},"in":"query","name":"since","required":false,"description":"Start of time range (ISO 8601)"},{"schema":{"type":"string","format":"date-time"},"in":"query","name":"until","required":false,"description":"End of time range (ISO 8601)"},{"schema":{"type":"integer","minimum":1,"maximum":300,"default":25},"in":"query","name":"limit","required":false,"description":"Number of results per page (1-300, default 25)"},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Pagination cursor from previous response"},{"name":"metadata","in":"query","required":false,"style":"deepObject","explode":true,"description":"Exact metadata key/value filters, e.g. metadata[order_id]=1234. Maximum 3 keys. Keys may contain letters, digits, dot, dash and underscore (up to 64 chars); values up to 200 chars.","schema":{"type":"object","maxProperties":3,"additionalProperties":{"type":"string"}}}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Paginated event list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventListPage"},"example":{"items":[{"id":"evt_1a2b3c4d5e6f7081","type":"delivered","message_id":"msg_a1b2c3d4e5f64789","recipient":"john@example.com","timestamp":"2026-08-16T09:30:04.000Z","tags":["onboarding"],"metadata":null,"created_at":"2026-08-16T09:30:04.000Z"},{"id":"evt_2b3c4d5e6f708192","type":"opened","message_id":"msg_a1b2c3d4e5f64789","recipient":"john@example.com","timestamp":"2026-08-16T10:02:11.000Z","tags":["onboarding"],"metadata":null,"created_at":"2026-08-16T10:02:11.000Z"},{"id":"evt_3c4d5e6f70819203","type":"clicked","message_id":"msg_a1b2c3d4e5f64789","recipient":"john@example.com","timestamp":"2026-08-16T10:02:35.000Z","tags":["onboarding"],"metadata":null,"created_at":"2026-08-16T10:02:35.000Z"}],"has_more":true,"next_cursor":"evt_3c4d5e6f70819203","total_count":148}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/events/export":{"post":{"operationId":"exportEvents","summary":"Export events","tags":["Events"],"description":"Start an async CSV export of the event log — the \"give me everything as a file\" alternative to paginating GET /v3/events. Accepts the same filters (event types, recipient, tag, up to 3 metadata key/values) over a time window capped at your plan’s event retention (30 days Free / 12 months paid). Runs as a background job: poll GET /v3/events/export/{id} until completed, then download the CSV (kept 7 days). Caps: 1,000,000 rows per export (narrow the window if exceeded), 3 concurrent jobs per account. Submitting an identical filter set while an equivalent job is pending/running returns the existing job instead of starting a duplicate.","requestBody":{"required":true,"content":{"application/json":{"schema":{"anyOf":[{"not":{}},{"type":"object","properties":{"since":{"type":"string","format":"date-time","description":"Window start (ISO 8601). Default: 30 days ago. May not reach past your plan’s event retention."},"until":{"type":"string","format":"date-time","description":"Window end (ISO 8601). Default: now."},"events":{"type":"array","items":{"type":"string","enum":["queued","sent","delivered","opened","clicked","bounced","complained","unsubscribed","delayed","rejected","failed","duplicate"]},"minItems":1,"description":"Only these event types. Default: all types."},"recipient":{"type":"string","maxLength":320,"description":"Only events for this recipient address (exact match)"},"tag":{"type":"string","maxLength":128,"description":"Only events carrying this tag (event or parent message)"},"metadata":{"type":"object","additionalProperties":{"type":"string","minLength":1,"maxLength":200},"description":"Metadata filters (AND semantics, exact match) — up to 3 key/value pairs, same limits as GET /v3/events"},"format":{"type":"string","enum":["csv"],"default":"csv","description":"Output format (csv only for now)"}},"additionalProperties":false}]},"example":{"since":"2026-08-01T00:00:00Z","until":"2026-08-31T23:59:59Z","events":["bounced","complained"],"tag":"billing","metadata":{"order_id":"1234"}}}}},"security":[{"bearerAuth":[]}],"responses":{"202":{"description":"Export accepted (or an equivalent in-flight job returned)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventExportAccepted"},"example":{"export_id":"7f3a2b1c-9d8e-4f50-a1b2-c3d4e5f60789","status":"pending"}}}},"400":{"description":"Window reaches past your plan’s event retention, or invalid filters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Too many concurrent exports (max 3 pending/running per account)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/events/export/{id}":{"get":{"operationId":"getEventExport","summary":"Get an event export","tags":["Events"],"description":"Status of an event export (pending | processing | completed | failed | expired); once completed, download_url serves the CSV (exports are kept 7 days). A failed job carries the reason in error_message — e.g. the 1,000,000-row cap.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Event export job","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportJob"},"example":{"id":"7f3a2b1c-9d8e-4f50-a1b2-c3d4e5f60789","status":"completed","filename":"events-2026-08-31.csv","row_count":48210,"download_url":"https://api.wemail.io/v3/events/export/7f3a2b1c-9d8e-4f50-a1b2-c3d4e5f60789/download","error_message":null,"created_at":"2026-08-31T09:00:00.000Z","completed_at":"2026-08-31T09:02:41.000Z","expires_at":"2026-09-07T09:02:41.000Z"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such export on your account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/events/export/{id}/download":{"get":{"operationId":"downloadEventExport","summary":"Download an event export","tags":["Events"],"description":"Download the generated CSV. Available once the export status is completed, until it expires (7 days).","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"CSV export of the event log (streamed as a file attachment).","headers":{"content-disposition":{"description":"attachment; filename=\"events-<date>.csv\"","schema":{"type":"string"}}},"content":{"text/csv":{"schema":{"type":"string","format":"binary"},"example":"at,event,message_id,recipient,tag\r\n2026-08-28T10:15:00.000Z,delivered,msg_a1b2c3,john@example.com,onboarding\r\n"}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such export, not finished, or expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/domains":{"get":{"operationId":"listDomains","summary":"List domains","tags":["Domains"],"description":"Retrieve all domains associated with the account.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Domain list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainListPage"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"operationId":"createDomain","summary":"Add a domain","tags":["Domains"],"description":"Register a new sending domain for the account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":"string","minLength":3,"maxLength":253,"pattern":"^[a-zA-Z0-9][a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$","description":"Domain name to register (e.g. mail.example.com)"}},"required":["domain"],"additionalProperties":false},"example":{"domain":"mail.example.com"}}}},"security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Domain created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Domain"},"example":{"id":"3f6b2a9e-1c4d-4e8f-9a0b-5c6d7e8f9a0b","domain":"mail.example.com","status":"pending","spf":false,"dkim":false,"dmarc":false,"region":"eu-west-1","dkim_selector":"wm1","tracking_subdomain":"track.mail.example.com","dns_records":[{"type":"TXT","host":"mail.example.com","value":"v=spf1 include:spf.wemail.io ~all","ok":false},{"type":"CNAME","host":"wm1._domainkey.mail.example.com","value":"wm1.dkim.wemail.io","ok":false}],"sent_count":0,"reputation":null,"created_at":"2026-08-16T09:30:00.000Z","verified_at":null}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Plan limit reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Domain already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/domains/{name}":{"get":{"operationId":"getDomain","summary":"Get domain details","tags":["Domains"],"description":"Retrieve details and DNS records for a specific domain.","parameters":[{"schema":{"type":"string"},"in":"path","name":"name","required":true,"description":"Domain name"}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Domain details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Domain"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Domain not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"delete":{"operationId":"deleteDomain","summary":"Delete a domain","tags":["Domains"],"description":"Remove a sending domain from the account.","parameters":[{"schema":{"type":"string"},"in":"path","name":"name","required":true,"description":"Domain name"}],"security":[{"bearerAuth":[]}],"responses":{"204":{"description":"Domain deleted"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Domain is shared from another workspace (owner-only action)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Domain not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/domains/{name}/verify":{"put":{"operationId":"verifyDomain","summary":"Verify domain DNS records","tags":["Domains"],"description":"Trigger DNS record verification for a domain.","parameters":[{"schema":{"type":"string"},"in":"path","name":"name","required":true,"description":"Domain name"}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Domain verification result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Domain"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Domain is shared from another workspace (owner-only action)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Domain not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/templates":{"get":{"operationId":"listTemplates","summary":"List templates","tags":["Templates"],"description":"Retrieve a paginated list of email templates.","parameters":[{"schema":{"type":"integer","minimum":1,"default":1},"in":"query","name":"page","required":false},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"in":"query","name":"per_page","required":false},{"schema":{"type":"string"},"in":"query","name":"category","required":false}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Paginated template list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateListPage"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"operationId":"createTemplate","summary":"Create a template","tags":["Templates"],"description":"Create a new email template.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"description":{"type":"string","maxLength":1000},"subject":{"type":"string","maxLength":998},"html":{"type":"string"},"text_body":{"type":"string"},"text":{"type":"string"},"vars_schema":{"type":"object","additionalProperties":{}},"category":{"type":"string","maxLength":100}},"required":["name"],"additionalProperties":false},"example":{"name":"welcome-email","subject":"Welcome, {{first_name}}!","html":"<h1>Hi {{first_name}}</h1><p>Great to have you on board.</p>","category":"onboarding"}}}},"security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Template created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Template"},"example":{"id":"tpl_9f8e7d6c5b4a3210","name":"welcome-email","description":null,"subject":"Welcome, {{first_name}}!","html":"<h1>Hi {{first_name}}</h1><p>Great to have you on board.</p>","text_body":null,"vars_schema":null,"version":1,"active":true,"category":"onboarding","created_at":"2026-08-16T09:30:00.000Z","updated_at":"2026-08-16T09:30:00.000Z"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/templates/{id}":{"get":{"operationId":"getTemplate","summary":"Get a template","tags":["Templates"],"description":"Retrieve a specific email template by ID.","parameters":[{"schema":{"type":"string","minLength":1},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Template details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Template"},"example":{"id":"tpl_9f8e7d6c5b4a3210","name":"welcome-email","description":null,"subject":"Welcome, {{first_name}}!","html":"<h1>Hi {{first_name}}</h1><p>Great to have you on board.</p>","text_body":null,"vars_schema":null,"version":1,"active":true,"category":"onboarding","created_at":"2026-08-16T09:30:00.000Z","updated_at":"2026-08-16T09:30:00.000Z"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"put":{"operationId":"updateTemplate","summary":"Update a template","tags":["Templates"],"description":"Update an existing email template.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"description":{"type":"string","maxLength":1000},"subject":{"type":"string","maxLength":998},"html":{"type":"string"},"text_body":{"type":"string"},"text":{"type":"string"},"vars_schema":{"type":"object","additionalProperties":{}},"active":{"type":"boolean"},"category":{"type":"string","maxLength":100}},"additionalProperties":false},"example":{"subject":"Welcome aboard, {{first_name}}!","html":"<h1>Hi {{first_name}}</h1><p>Great to have you on board. Let us show you around.</p>"}}}},"parameters":[{"schema":{"type":"string","minLength":1},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Template updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Template"},"example":{"id":"tpl_9f8e7d6c5b4a3210","name":"welcome-email","description":null,"subject":"Welcome aboard, {{first_name}}!","html":"<h1>Hi {{first_name}}</h1><p>Great to have you on board. Let us show you around.</p>","text_body":null,"vars_schema":null,"version":2,"active":true,"category":"onboarding","created_at":"2026-08-16T09:30:00.000Z","updated_at":"2026-08-17T14:12:00.000Z"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"delete":{"operationId":"deleteTemplate","summary":"Delete a template","tags":["Templates"],"description":"Delete an email template by ID.","parameters":[{"schema":{"type":"string","minLength":1},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Template deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteConfirmation"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Template not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/keys":{"get":{"operationId":"listApiKeys","summary":"List API keys","tags":["API Keys"],"description":"Retrieve a paginated list of API keys (prefix only, full key not returned).","parameters":[{"schema":{"type":"integer","minimum":1,"default":1},"in":"query","name":"page","required":false},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"in":"query","name":"per_page","required":false}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Paginated API key list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyListPage"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"operationId":"createApiKey","summary":"Create an API key","tags":["API Keys"],"description":"Create a new API key. The full key is returned only once at creation time.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"scope":{"type":"string","enum":["full_access","send_only","read_only"],"default":"full_access"},"env":{"type":"string","enum":["live","test"],"default":"live"},"verify_before_send":{"type":"boolean","nullable":true},"test_outcomes":{"type":"object","properties":{"send":{"type":"object","additionalProperties":{"type":"number","minimum":0}},"verify":{"type":"object","additionalProperties":{"type":"number","minimum":0}}},"additionalProperties":false}},"required":["name"],"additionalProperties":false},"example":{"name":"Production server key","scope":"send_only","env":"live","verify_before_send":false}}}},"security":[{"bearerAuth":[]}],"responses":{"201":{"description":"API key created (full key returned once)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Plan limit reached (API key cap for the current plan)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/keys/{id}":{"patch":{"operationId":"updateApiKey","summary":"Update API key settings","tags":["API Keys"],"description":"Update settings on an existing API key.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"scope":{"type":"string","enum":["full_access","send_only","read_only"]},"verify_before_send":{"type":"boolean","nullable":true},"test_outcomes":{"type":"object","properties":{"send":{"type":"object","additionalProperties":{"type":"number","minimum":0}},"verify":{"type":"object","additionalProperties":{"type":"number","minimum":0}}},"additionalProperties":false},"ip_allowlist":{"type":"array","items":{"type":"object","properties":{"cidr":{"type":"string","minLength":1,"description":"IPv4/IPv6 CIDR (bare IPs are normalized to /32 or /128)"},"label":{"type":"string","maxLength":100,"default":"","description":"Optional human-readable label"}},"required":["cidr"],"additionalProperties":false},"maxItems":50,"description":"Source-IP allowlist for this key. Empty array = no restriction. Max 50 entries."}},"additionalProperties":false},"example":{"name":"Production server key (rotated)","scope":"full_access","ip_allowlist":[{"cidr":"203.0.113.0/24","label":"Office"}]}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"API key updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyDetail"}}}},"400":{"description":"Validation error (e.g. invalid CIDR in ip_allowlist)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Console session lacks owner/admin role for ip_allowlist changes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"API key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"delete":{"operationId":"revokeApiKey","summary":"Revoke an API key","tags":["API Keys"],"description":"Permanently revoke an API key.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"API key revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteConfirmation"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"API key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/keys/{id}/rotate":{"post":{"operationId":"rotateApiKey","summary":"Rotate an API key","tags":["API Keys"],"description":"Rotate an API key in place: the key keeps its id and settings but receives a brand-new secret. The previous secret stops working immediately. The new full key is returned only once in this response. Console sessions must be an account owner or admin to rotate keys; API-key callers need a full-access key.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"API key rotated (new full key returned once)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyRotated"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Insufficient role/scope to rotate keys","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"API key not found or revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/suppressions/{type}":{"get":{"operationId":"listSuppressions","summary":"List suppressions by type","tags":["Suppressions"],"description":"Retrieve a paginated list of suppressed addresses for a given type.","parameters":[{"schema":{"type":"integer","minimum":1,"default":1},"in":"query","name":"page","required":false},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"in":"query","name":"per_page","required":false},{"schema":{"type":"string","maxLength":200},"in":"query","name":"search","required":false},{"schema":{"type":"string","format":"date-time"},"in":"query","name":"since","required":false,"description":"Filter entries created at or after this instant (ISO 8601 date-time). Preferred — consistent with GET /events."},{"schema":{"type":"string","format":"date-time"},"in":"query","name":"until","required":false,"description":"Filter entries created at or before this instant (ISO 8601 date-time). Preferred — consistent with GET /events."},{"schema":{"type":"string","format":"date"},"in":"query","name":"from_date","required":false,"description":"Deprecated — use `since`. Filter entries created on or after this UTC calendar day (YYYY-MM-DD)."},{"schema":{"type":"string","format":"date"},"in":"query","name":"to_date","required":false,"description":"Deprecated — use `until`. Filter entries created on or before this UTC calendar day (YYYY-MM-DD, inclusive to end-of-day)."},{"schema":{"type":"integer"},"in":"query","name":"from","required":false,"description":"Deprecated — use `since`. Lower bound as epoch milliseconds."},{"schema":{"type":"integer"},"in":"query","name":"to","required":false,"description":"Deprecated — use `until`. Upper bound as epoch milliseconds."},{"schema":{"type":"string","enum":["bounces","unsubscribes","complaints","allowlist"]},"in":"path","name":"type","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Paginated suppression list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuppressionListPage"},"example":{"items":[{"id":"5d4c3b2a-1f0e-4d9c-8b7a-6e5f4d3c2b1a","account_id":"3f6b2a9e-1c4d-4e8f-9a0b-5c6d7e8f9a0b","type":"bounces","email":"john@example.com","reason":"Hard bounce","source":"delivery_report","override_unsubscribes":false,"created_at":"2026-08-16T09:30:00.000Z"}],"pagination":{"page":1,"per_page":25,"total_count":1,"total_pages":1}}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"operationId":"createSuppression","summary":"Add a suppression entry","tags":["Suppressions"],"description":"Add an email address to the suppression list. Returns 201 if created, 200 if already exists.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"reason":{"type":"string","maxLength":500},"source":{"type":"string","enum":["manual","import"]},"override_unsubscribes":{"type":"boolean","description":"Allowlist entries only: also deliver when the address has unsubscribed. Default false — an unsubscribe normally wins over the allowlist. Only for inboxes you own."}},"required":["email"],"additionalProperties":false},"example":{"email":"john@example.com","reason":"Hard bounce","source":"manual"}}}},"parameters":[{"schema":{"type":"string","enum":["bounces","unsubscribes","complaints","allowlist"]},"in":"path","name":"type","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Suppression entry already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Suppression"}}}},"201":{"description":"Suppression entry created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Suppression"},"example":{"id":"5d4c3b2a-1f0e-4d9c-8b7a-6e5f4d3c2b1a","account_id":"3f6b2a9e-1c4d-4e8f-9a0b-5c6d7e8f9a0b","type":"bounces","email":"john@example.com","reason":"Hard bounce","source":"manual","override_unsubscribes":false,"created_at":"2026-08-16T09:30:00.000Z"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/suppressions/{type}/{address}":{"delete":{"operationId":"deleteSuppression","summary":"Remove a suppression entry","tags":["Suppressions"],"description":"Remove an email address from the suppression list.","parameters":[{"schema":{"type":"string","enum":["bounces","unsubscribes","complaints","allowlist"]},"in":"path","name":"type","required":true},{"schema":{"type":"string","minLength":1},"in":"path","name":"address","required":true,"description":"The suppressed address (matched case-insensitively; may be a non-RFC address as reported by the provider)"}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Suppression entry removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteConfirmation"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Suppression entry not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/suppressions/{type}/bulk":{"post":{"operationId":"bulkCreateSuppressions","summary":"Bulk add suppression entries","tags":["Suppressions"],"description":"Add up to 1,000 addresses to a suppression list in one call. Idempotent like the single endpoint: an entry that already exists counts as added. Invalid rows are skipped and reported per-index (invalid_syntax, duplicate_in_batch) — the rest of the batch proceeds. For files, use the CSV import endpoint instead.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"entries":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string","minLength":3,"maxLength":320,"description":"Address to suppress (lowercased on write)"},"reason":{"type":"string","maxLength":500},"override_unsubscribes":{"type":"boolean","description":"Allowlist only — also deliver when the address has unsubscribed"}},"required":["email"],"additionalProperties":false},"minItems":1,"maxItems":1000,"description":"Entries to add (1-1,000)"}},"required":["entries"],"additionalProperties":false},"example":{"entries":[{"email":"bounce@example.com","reason":"Hard bounce"},{"email":"complaint@example.com","reason":"Marked as spam"}]}}}},"parameters":[{"schema":{"type":"string","enum":["bounces","unsubscribes","complaints","allowlist"]},"in":"path","name":"type","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Per-batch result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkSuppressionResult"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/suppressions/{type}/bulk.delete":{"post":{"operationId":"bulkDeleteSuppressions","summary":"Bulk remove suppression entries","tags":["Suppressions"],"description":"Remove up to 1,000 addresses from a suppression list in one call. Addresses are deduplicated and lowercased; the response reports how many rows were removed vs not found.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"emails":{"type":"array","items":{"type":"string","minLength":3,"maxLength":320},"minItems":1,"maxItems":1000,"description":"Addresses to remove (1-1,000)"}},"required":["emails"],"additionalProperties":false},"example":{"emails":["bounce@example.com","old-address@example.com"]}}}},"parameters":[{"schema":{"type":"string","enum":["bounces","unsubscribes","complaints","allowlist"]},"in":"path","name":"type","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Removal counts (after in-batch dedupe)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkSuppressionDeleteResult"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/suppressions/{type}/import":{"post":{"operationId":"importSuppressions","summary":"Import suppressions from CSV","tags":["Suppressions"],"description":"Upload a CSV (multipart/form-data, field name `file`, up to 25 MB) and import every row into this suppression list as an async job. Column 1 = email, optional column 2 = reason; a header row containing \"email\" is skipped. Rows import with source=import in 1,000-row batches; invalid rows are collected into a per-row error report (capped at 500). Poll the returned import with GET /v3/suppressions/imports/{id}.","parameters":[{"schema":{"type":"string","enum":["bounces","unsubscribes","complaints","allowlist"]},"in":"path","name":"type","required":true}],"security":[{"bearerAuth":[]}],"responses":{"202":{"description":"Import accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuppressionImportAccepted"}}}},"400":{"description":"Validation error (missing file, not CSV, too large)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"CSV file (up to 25 MB). Column 1 = email, optional column 2 = reason; a header row containing \"email\" is skipped."}}}}}}}},"/suppressions/imports/{id}":{"get":{"operationId":"getSuppressionImport","summary":"Get a suppression import","tags":["Suppressions"],"description":"Status and per-row error report of a CSV suppression import.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Import job","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuppressionImport"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such import on your account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/suppressions/{type}/export":{"post":{"operationId":"exportSuppressions","summary":"Export a suppression list","tags":["Suppressions"],"description":"Start an async CSV export of this suppression list (full size — no pagination cap). Optional since/until bound the entry creation date; source filters import vs manual entries. Poll GET /v3/suppressions/exports/{id} for the download.","requestBody":{"required":true,"content":{"application/json":{"schema":{"anyOf":[{"not":{}},{"type":"object","properties":{"since":{"type":"string","format":"date-time","description":"Only entries created at/after this instant"},"until":{"type":"string","format":"date-time","description":"Only entries created before this instant"},"source":{"type":"string","enum":["manual","import","delivery_report","verification"],"description":"Only entries from this source"}},"additionalProperties":false}]},"example":{"since":"2026-08-01T00:00:00Z","until":"2026-08-31T23:59:59Z"}}}},"parameters":[{"schema":{"type":"string","enum":["bounces","unsubscribes","complaints","allowlist"]},"in":"path","name":"type","required":true}],"security":[{"bearerAuth":[]}],"responses":{"202":{"description":"Export accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuppressionExportAccepted"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/suppressions/exports/{id}":{"get":{"operationId":"getSuppressionExport","summary":"Get a suppression export","tags":["Suppressions"],"description":"Status of a suppression export (pending | processing | completed | failed | expired); once completed, download_url serves the CSV (exports are kept 7 days).","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Export job","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportJob"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such export on your account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/suppressions/exports/{id}/download":{"get":{"operationId":"downloadSuppressionExport","summary":"Download a suppression export","tags":["Suppressions"],"description":"Download the generated CSV. Available once the export status is completed, until it expires (7 days).","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"CSV export of the suppression list (streamed as a file attachment).","headers":{"content-disposition":{"description":"attachment; filename=\"suppressions-<type>-<date>.csv\"","schema":{"type":"string"}}},"content":{"text/csv":{"schema":{"type":"string","format":"binary"},"example":"email,reason,source,created_at\r\njohn@example.com,hard_bounce,import,2026-08-28T10:15:00.000Z\r\n"}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such export, not finished, or expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/data-removals":{"post":{"operationId":"createDataRemoval","summary":"Request GDPR erasure of a recipient","tags":["Data removals"],"description":"Erase a recipient's personal data across the account (GDPR right to erasure): the recipient's encryption key is destroyed (crypto-shredding) so their address, and the subject, bodies, variables and headers of every message addressed only to them, become unreadable everywhere; event logs are anonymised, email-verification results deleted, download attribution cleared and export files embedding the address purged. Irreversible and audit-logged. A suppression entry flagged source=gdpr_erasure is retained so the person is never mailed again, and the removal record itself is kept as proof of erasure. Requires a full_access key. Poll GET /v3/data-removals/{id} for completion.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":320,"description":"The data subject's email address"},"notify_webhook_url":{"type":"string","format":"uri","maxLength":2000,"description":"Optional URL to POST {removal_id, status, completed_at} to when the erasure finishes (best-effort)"}},"required":["email"],"additionalProperties":false},"example":{"email":"user@example.com","notify_webhook_url":"https://example.com/webhooks/gdpr"}}}},"security":[{"bearerAuth":[]}],"responses":{"202":{"description":"Erasure accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataRemovalAccepted"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Key scope does not allow mutations (full_access required)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/data-removals/{id}":{"get":{"operationId":"getDataRemoval","summary":"Get data-removal status","tags":["Data removals"],"description":"Poll an erasure request: pending → processing → done (with per-table redaction counts) or failed.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Removal status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataRemoval"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such removal on this account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/webhooks":{"get":{"operationId":"listWebhooks","summary":"List webhooks","tags":["Webhooks"],"description":"Retrieve a paginated list of webhook endpoints.","parameters":[{"schema":{"type":"integer","minimum":1,"default":1},"in":"query","name":"page","required":false},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"in":"query","name":"per_page","required":false}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Paginated webhook list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookListPage"},"example":{"items":[{"id":"8f7f0a3e-4a2b-4c9d-9e1f-2a3b4c5d6e7f","url":"https://example.com/hooks/wemail","events":["delivered","bounced","complained"],"enabled":true,"status":"active","disabled_reason":null,"disabled_at":null,"domain_id":null,"domain":null,"auth":{"type":"hmac","secret_set":true,"secret_hint":"…5c4d","header_name":"X-Wemail-Signature"},"created_at":"2026-08-16T09:30:00.000Z","updated_at":"2026-08-16T09:30:00.000Z"}],"pagination":{"page":1,"per_page":25,"total_count":1,"total_pages":1}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"externalDocs":{"description":"Webhook payloads, signature verification and retry behavior","url":"https://wemail.io/docs/webhooks"}},"post":{"operationId":"createWebhook","summary":"Create a webhook","tags":["Webhooks"],"description":"Register a new webhook endpoint to receive event notifications.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"allOf":[{"type":"string"},{"type":"string","format":"uri"}]},"events":{"type":"array","items":{"type":"string","enum":["queued","sent","delivered","opened","clicked","bounced","complained","unsubscribed","delayed","rejected","failed","message.queued","message.sent","message.delivered","message.opened","message.clicked","message.bounced","message.complained","message.unsubscribed","message.delayed","message.rejected","message.failed"],"description":"Webhook event type. Canonical values: queued, sent, delivered, opened, clicked, bounced, complained, unsubscribed, delayed, rejected, failed. The legacy `message.<type>` spelling is also accepted and normalized to the canonical name."},"minItems":1},"enabled":{"type":"boolean","default":true},"secret":{"type":"string","minLength":32,"maxLength":512},"domain_id":{"type":"string","format":"uuid","nullable":true},"auth":{"type":"object","properties":{"type":{"type":"string","enum":["none","bearer","basic","custom_headers","oauth2","hmac","mtls"],"default":"none","description":"Authentication scheme the receiving endpoint expects: none (default), bearer, basic, custom_headers, oauth2 (client credentials), hmac (recommended — per-endpoint HMAC-SHA256 signature), or mtls (client certificate). Set to \"none\" to remove a previously configured auth."},"token":{"type":"string","minLength":1,"maxLength":4096,"pattern":"^[^\\r\\n]*$","description":"Fixed bearer token (required when auth.type is \"bearer\"), sent as `Authorization: Bearer <token>`. Write-only: never returned; reads echo `token_set`."},"username":{"type":"string","minLength":1,"maxLength":256,"description":"HTTP Basic username (required when auth.type is \"basic\")."},"password":{"type":"string","minLength":1,"maxLength":1024,"description":"HTTP Basic password (required when auth.type is \"basic\"). Write-only: never returned; reads echo `password_set`."},"headers":{"type":"object","additionalProperties":{"type":"string","maxLength":1024,"pattern":"^[^\\r\\n]*$"},"description":"Custom header name→value pairs sent with every delivery (auth.type \"custom_headers\"). Max 5 headers; names use RFC 7230 token characters (max 64 chars); values max 1024 chars, no CR/LF. Content-Type, Content-Length, Host and the wemail signature headers cannot be overridden. Values are write-only: reads echo the header names with masked values."},"token_url":{"type":"string","format":"uri","maxLength":2048,"description":"OAuth 2.0 client-credentials token endpoint (required when auth.type is \"oauth2\"). HTTPS only; must resolve to a public address."},"client_id":{"type":"string","minLength":1,"maxLength":256,"description":"OAuth 2.0 client id (required when auth.type is \"oauth2\")."},"client_secret":{"type":"string","minLength":1,"maxLength":1024,"description":"OAuth 2.0 client secret (required when auth.type is \"oauth2\"). Write-only: never returned; reads echo `client_secret_set`."},"scope":{"type":"string","maxLength":512,"description":"Optional OAuth 2.0 scope included in the token request."},"audience":{"type":"string","maxLength":512,"description":"Optional OAuth 2.0 audience included in the token request."},"secret":{"type":"string","pattern":"^whsec_[A-Za-z0-9+/=]{32,88}$","description":"Per-endpoint HMAC signing secret (auth.type \"hmac\"): `whsec_` + base64. Omit to have the server generate one (`whsec_` + base64 of 32 random bytes) — the secret is returned in PLAINTEXT exactly once, in the create/update response that generated or accepted it. All later reads echo only `secret_set` and `secret_hint`. The signature algorithm is fixed HMAC-SHA256 and the documented replay tolerance is fixed at 5 minutes."},"header_name":{"type":"string","pattern":"^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,64}$","description":"Header that carries the HMAC signature (auth.type \"hmac\"). Default \"X-Wemail-Signature\". RFC 7230 token characters, max 64 chars; must not collide with Content-Type/Host, the wemail signature headers, or the fixed X-Wemail-Timestamp / X-Wemail-Key-Id companions."},"rotate":{"type":"boolean","description":"Update only, auth.type \"hmac\" only: generate a fresh signing secret and keep the previous one valid for 24 hours (deliveries carry BOTH `v1=` signatures during the window). The new secret is returned in plaintext exactly once in the update response. Cannot be combined with an explicit auth.secret."},"client_cert":{"type":"string","maxLength":16384,"description":"PEM-encoded X.509 client certificate presented in the TLS handshake (auth.type \"mtls\"). Max 16 KB. Validated at save time (parseable, matches auth.client_key, not expired). Reads echo `cert_set` plus the certificate fingerprint, subject CN and expiry."},"client_key":{"type":"string","maxLength":16384,"description":"PEM-encoded private key for the client certificate (auth.type \"mtls\"). Max 16 KB. Write-only: never returned; reads echo `key_set`."}},"additionalProperties":false,"description":"Optional authentication the delivery requests present to your endpoint. Omit (or set type \"none\") for unauthenticated deliveries. Secrets are stored encrypted and never returned by reads; on update, omitting a secret while keeping the same type preserves the stored value."}},"required":["url","events"],"additionalProperties":false},"example":{"url":"https://example.com/hooks/wemail","events":["delivered","bounced","complained"]}}}},"security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Webhook created (signing secret returned only this once)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookWithSecret"},"example":{"id":"8f7f0a3e-4a2b-4c9d-9e1f-2a3b4c5d6e7f","url":"https://example.com/hooks/wemail","events":["delivered","bounced","complained"],"enabled":true,"status":"active","disabled_reason":null,"disabled_at":null,"domain_id":null,"domain":null,"created_at":"2026-08-16T09:30:00.000Z","updated_at":"2026-08-16T09:30:00.000Z","secret":"whsec_4c2f1e0d9b8a7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Plan limit reached (webhook endpoint cap for the current plan)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"externalDocs":{"description":"Webhook payloads, signature verification and retry behavior","url":"https://wemail.io/docs/webhooks"}}},"/webhooks/{id}":{"put":{"operationId":"updateWebhook","summary":"Update a webhook","tags":["Webhooks"],"description":"Update an existing webhook endpoint configuration.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"allOf":[{"type":"string"},{"type":"string","format":"uri"}]},"events":{"type":"array","items":{"type":"string","enum":["queued","sent","delivered","opened","clicked","bounced","complained","unsubscribed","delayed","rejected","failed","message.queued","message.sent","message.delivered","message.opened","message.clicked","message.bounced","message.complained","message.unsubscribed","message.delayed","message.rejected","message.failed"],"description":"Webhook event type. Canonical values: queued, sent, delivered, opened, clicked, bounced, complained, unsubscribed, delayed, rejected, failed. The legacy `message.<type>` spelling is also accepted and normalized to the canonical name."},"minItems":1},"enabled":{"type":"boolean"},"domain_id":{"type":"string","format":"uuid","nullable":true},"auth":{"type":"object","properties":{"type":{"type":"string","enum":["none","bearer","basic","custom_headers","oauth2","hmac","mtls"],"default":"none","description":"Authentication scheme the receiving endpoint expects: none (default), bearer, basic, custom_headers, oauth2 (client credentials), hmac (recommended — per-endpoint HMAC-SHA256 signature), or mtls (client certificate). Set to \"none\" to remove a previously configured auth."},"token":{"type":"string","minLength":1,"maxLength":4096,"pattern":"^[^\\r\\n]*$","description":"Fixed bearer token (required when auth.type is \"bearer\"), sent as `Authorization: Bearer <token>`. Write-only: never returned; reads echo `token_set`."},"username":{"type":"string","minLength":1,"maxLength":256,"description":"HTTP Basic username (required when auth.type is \"basic\")."},"password":{"type":"string","minLength":1,"maxLength":1024,"description":"HTTP Basic password (required when auth.type is \"basic\"). Write-only: never returned; reads echo `password_set`."},"headers":{"type":"object","additionalProperties":{"type":"string","maxLength":1024,"pattern":"^[^\\r\\n]*$"},"description":"Custom header name→value pairs sent with every delivery (auth.type \"custom_headers\"). Max 5 headers; names use RFC 7230 token characters (max 64 chars); values max 1024 chars, no CR/LF. Content-Type, Content-Length, Host and the wemail signature headers cannot be overridden. Values are write-only: reads echo the header names with masked values."},"token_url":{"type":"string","format":"uri","maxLength":2048,"description":"OAuth 2.0 client-credentials token endpoint (required when auth.type is \"oauth2\"). HTTPS only; must resolve to a public address."},"client_id":{"type":"string","minLength":1,"maxLength":256,"description":"OAuth 2.0 client id (required when auth.type is \"oauth2\")."},"client_secret":{"type":"string","minLength":1,"maxLength":1024,"description":"OAuth 2.0 client secret (required when auth.type is \"oauth2\"). Write-only: never returned; reads echo `client_secret_set`."},"scope":{"type":"string","maxLength":512,"description":"Optional OAuth 2.0 scope included in the token request."},"audience":{"type":"string","maxLength":512,"description":"Optional OAuth 2.0 audience included in the token request."},"secret":{"type":"string","pattern":"^whsec_[A-Za-z0-9+/=]{32,88}$","description":"Per-endpoint HMAC signing secret (auth.type \"hmac\"): `whsec_` + base64. Omit to have the server generate one (`whsec_` + base64 of 32 random bytes) — the secret is returned in PLAINTEXT exactly once, in the create/update response that generated or accepted it. All later reads echo only `secret_set` and `secret_hint`. The signature algorithm is fixed HMAC-SHA256 and the documented replay tolerance is fixed at 5 minutes."},"header_name":{"type":"string","pattern":"^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,64}$","description":"Header that carries the HMAC signature (auth.type \"hmac\"). Default \"X-Wemail-Signature\". RFC 7230 token characters, max 64 chars; must not collide with Content-Type/Host, the wemail signature headers, or the fixed X-Wemail-Timestamp / X-Wemail-Key-Id companions."},"rotate":{"type":"boolean","description":"Update only, auth.type \"hmac\" only: generate a fresh signing secret and keep the previous one valid for 24 hours (deliveries carry BOTH `v1=` signatures during the window). The new secret is returned in plaintext exactly once in the update response. Cannot be combined with an explicit auth.secret."},"client_cert":{"type":"string","maxLength":16384,"description":"PEM-encoded X.509 client certificate presented in the TLS handshake (auth.type \"mtls\"). Max 16 KB. Validated at save time (parseable, matches auth.client_key, not expired). Reads echo `cert_set` plus the certificate fingerprint, subject CN and expiry."},"client_key":{"type":"string","maxLength":16384,"description":"PEM-encoded private key for the client certificate (auth.type \"mtls\"). Max 16 KB. Write-only: never returned; reads echo `key_set`."}},"additionalProperties":false,"description":"Optional authentication the delivery requests present to your endpoint. Omit (or set type \"none\") for unauthenticated deliveries. Secrets are stored encrypted and never returned by reads; on update, omitting a secret while keeping the same type preserves the stored value."}},"additionalProperties":false},"example":{"events":["delivered","bounced","complained","opened"],"enabled":true}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Webhook updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"},"example":{"id":"8f7f0a3e-4a2b-4c9d-9e1f-2a3b4c5d6e7f","url":"https://example.com/hooks/wemail","events":["delivered","bounced","complained","opened"],"enabled":true,"status":"active","disabled_reason":null,"disabled_at":null,"domain_id":null,"domain":null,"auth":{"type":"hmac","secret_set":true,"secret_hint":"…5c4d","header_name":"X-Wemail-Signature"},"created_at":"2026-08-16T09:30:00.000Z","updated_at":"2026-08-17T14:12:00.000Z"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"externalDocs":{"description":"Webhook payloads, signature verification and retry behavior","url":"https://wemail.io/docs/webhooks"}},"delete":{"operationId":"deleteWebhook","summary":"Delete a webhook","tags":["Webhooks"],"description":"Remove a webhook endpoint.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Webhook deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteConfirmation"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"externalDocs":{"description":"Webhook payloads, signature verification and retry behavior","url":"https://wemail.io/docs/webhooks"}}},"/webhooks/{id}/deliveries":{"get":{"operationId":"listWebhookDeliveries","summary":"List webhook deliveries","tags":["Webhooks"],"description":"List an endpoint’s delivery history — one row per logical delivery (the latest attempt of each event), newest first, cursor-paginated. Each item carries the DERIVED delivery state (`delivered`, `failed`, `pending`, `retrying`) plus `webhook_id`/`event_id`, so a page of failures pipes directly into `POST /v3/webhooks/deliveries/resend`. Defaults to the last 24 hours; `since`/`until` widen the window up to 90 days back. Endpoint-test attempts (no source event) are excluded.","parameters":[{"schema":{"type":"string","pattern":"^(delivered|failed|pending|retrying)(,(delivered|failed|pending|retrying))*$"},"in":"query","name":"status","required":false,"description":"Comma-separated delivery states to include: `delivered`, `failed`, `pending`, `retrying`. Omit for all states. Example: `failed,retrying`."},{"schema":{"type":"string","format":"date-time"},"in":"query","name":"since","required":false,"description":"Window start (ISO 8601). Default: 24 hours ago; clamped to at most 90 days back."},{"schema":{"type":"string","format":"date-time"},"in":"query","name":"until","required":false,"description":"Window end (ISO 8601). Default: now."},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Pagination cursor from the previous response (`next_cursor`)."},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"in":"query","name":"limit","required":false,"description":"Number of results per page (1-100, default 25)."},{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Cursor-paginated delivery list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryListPage"},"example":{"items":[{"webhook_id":"3f6b2a9e-1c4d-4e8f-9a0b-5c6d7e8f9a0b","event_id":"evt_a1b2c3d4e5f6","event":"bounced","attempts":7,"last_status":500,"last_attempt_at":"2026-08-30T09:15:00.000Z","state":"failed"},{"webhook_id":"3f6b2a9e-1c4d-4e8f-9a0b-5c6d7e8f9a0b","event_id":"evt_9z8y7x6w5v4u","event":"delivered","attempts":1,"last_status":200,"last_attempt_at":"2026-08-30T09:12:41.000Z","state":"delivered"}],"has_more":true,"next_cursor":"2026-08-30T09:12:41.000Z|evt_9z8y7x6w5v4u","total_count":132}}}},"400":{"description":"Validation error (bad status list, timestamp or cursor)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"externalDocs":{"description":"Webhook payloads, signature verification and retry behavior","url":"https://wemail.io/docs/webhooks"}}},"/webhooks/{id}/test":{"post":{"operationId":"testWebhookEndpoint","summary":"Test a webhook endpoint","tags":["Webhooks"],"description":"Fire a sample payload (marked `\"test\": true`) at the endpoint through the REAL delivery pipeline: the same Ed25519 platform signature, the endpoint’s receiver-side auth, the worker’s 30-second timeout and SSRF protections. The attempt is recorded in the endpoint’s delivery history (Console → Webhooks → Activity) as a test row. Works with test keys. Rate limited to 10 calls/minute.","requestBody":{"required":false,"description":"Optional. Omit the body to simulate a delivered event.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"event":{"type":"string","enum":["queued","sent","delivered","opened","clicked","bounced","complained","unsubscribed","delayed","rejected","failed"],"default":"delivered","description":"Webhook event type to simulate (default `delivered`)"}}},"example":{"event":"delivered"}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Test delivery results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestResult"},"example":{"deliveries":[{"event":"delivered","status":200,"latency_ms":184,"success":true}]}}}},"400":{"description":"Validation error (unknown event type, blocked endpoint URL, or unresolvable receiver auth)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Webhook not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"externalDocs":{"description":"Webhook payloads, signature verification and retry behavior","url":"https://wemail.io/docs/webhooks"}}},"/webhooks/deliveries/resend":{"post":{"operationId":"resendWebhookDeliveries","summary":"Resend webhook deliveries","tags":["Webhooks"],"description":"Manually resend up to 100 failed webhook deliveries, optionally REDIRECTED to another endpoint of your account. Only deliveries whose latest attempt is failed (and that came from a real event — not endpoint tests) are eligible; everything else is reported back as skipped with a reason. Each accepted delivery is re-posted exactly once in the background (no automatic retries), appears as a new manual attempt in the delivery history, and redirected resends record the original endpoint and the time of the redirect. Every call is written to the account audit trail (`GET /v3/audit`). Duplicate submissions of the same delivery within 60 seconds are deduplicated (`locked`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"deliveries":{"type":"array","items":{"type":"object","properties":{"webhook_id":{"type":"string","format":"uuid","description":"The endpoint the delivery belongs to"},"event_id":{"type":"string","minLength":1,"maxLength":200,"description":"The event whose delivery to resend"}},"required":["webhook_id","event_id"],"additionalProperties":false},"minItems":1,"maxItems":100,"description":"Exact deliveries to resend (1-100). Use `query` instead to let the server find them."},"query":{"type":"object","properties":{"webhook_id":{"type":"string","format":"uuid","description":"Only deliveries to this endpoint (default: all your endpoints)"},"event":{"type":"string","minLength":1,"maxLength":50,"description":"Only deliveries of this event type (e.g. bounced, delivered)"},"since":{"type":"string","format":"date-time","description":"Window start (ISO 8601). Default: 24 hours ago; at most 90 days back"},"until":{"type":"string","format":"date-time","description":"Window end (ISO 8601). Default: now"},"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Resend at most this many (newest first). Default 100 — check has_more and repeat the call to drain the rest"}},"additionalProperties":false,"description":"Find-and-resend: the server locates every FAILED delivery matching these filters — no need to list deliveries first"},"target_webhook_id":{"type":"string","format":"uuid","description":"Redirect: post every resend to THIS endpoint of your account instead of each delivery’s original endpoint. Must be an enabled endpoint."}},"additionalProperties":false},"example":{"deliveries":[{"webhook_id":"3f6b2a9e-1c4d-4e8f-9a0b-5c6d7e8f9a0b","event_id":"evt_a1b2c3d4e5f6"},{"webhook_id":"3f6b2a9e-1c4d-4e8f-9a0b-5c6d7e8f9a0b","event_id":"evt_9z8y7x6w5v4u"}]}}}},"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Per-delivery acceptance detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResendResult"},"example":{"results":[{"webhook_id":"3f6b2a9e-1c4d-4e8f-9a0b-5c6d7e8f9a0b","event_id":"evt_a1b2c3d4e5f6","accepted":true,"reason":null},{"webhook_id":"3f6b2a9e-1c4d-4e8f-9a0b-5c6d7e8f9a0b","event_id":"evt_9z8y7x6w5v4u","accepted":false,"reason":"not_failed"}],"accepted_count":1,"skipped_count":1,"matched_count":2,"has_more":false}}}},"400":{"description":"Validation error (unknown or disabled target endpoint)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"externalDocs":{"description":"Webhook payloads, signature verification and retry behavior","url":"https://wemail.io/docs/webhooks"}}},"/lists":{"get":{"operationId":"listMailingLists","summary":"List mailing lists","tags":["Lists"],"description":"Retrieve a paginated list of mailing lists.","parameters":[{"schema":{"type":"integer","minimum":1,"default":1},"in":"query","name":"page","required":false},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"in":"query","name":"per_page","required":false}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Paginated mailing list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MailingListPage"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"operationId":"createMailingList","summary":"Create a mailing list","tags":["Lists"],"description":"Create a new mailing list.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"address":{"type":"string","format":"email"},"name":{"type":"string","minLength":1,"maxLength":255},"description":{"type":"string","maxLength":1000}},"required":["address","name"],"additionalProperties":false},"example":{"address":"newsletter@example.com","name":"Monthly Newsletter","description":"Product updates and tips"}}}},"security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Mailing list created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MailingList"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"A mailing list with this address already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/lists/{address}/members":{"get":{"operationId":"listMailingListMembers","summary":"List members of a mailing list","tags":["Lists"],"description":"Retrieve a paginated list of members for a specific mailing list.","parameters":[{"schema":{"type":"integer","minimum":1,"default":1},"in":"query","name":"page","required":false},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"in":"query","name":"per_page","required":false},{"schema":{"type":"boolean"},"in":"query","name":"subscribed","required":false},{"schema":{"type":"string","format":"email"},"in":"path","name":"address","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Paginated member list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MailingListMemberPage"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Mailing list not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"operationId":"addMailingListMember","summary":"Add a member to a mailing list","tags":["Lists"],"description":"Add a new member to a specific mailing list.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"address":{"type":"string","format":"email"},"name":{"type":"string","maxLength":255},"vars":{"type":"object","additionalProperties":{}},"subscribed":{"type":"boolean","default":true}},"required":["address"],"additionalProperties":false},"example":{"address":"john@example.com","name":"John Doe","vars":{"first_name":"John"},"subscribed":true}}}},"parameters":[{"schema":{"type":"string","format":"email"},"in":"path","name":"address","required":true}],"security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Member added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListMember"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Mailing list not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/lists/{address}/members/{member}":{"delete":{"operationId":"removeMailingListMember","summary":"Remove a member from a mailing list","tags":["Lists"],"description":"Remove a specific member from a mailing list.","parameters":[{"schema":{"type":"string","format":"email"},"in":"path","name":"address","required":true},{"schema":{"type":"string","format":"email"},"in":"path","name":"member","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Member removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteConfirmation"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Member not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/uploads":{"post":{"operationId":"createUpload","summary":"Upload a hosted file","tags":["Uploads"],"description":"Upload a file (multipart/form-data, field name `file`, up to 25 MB) to host it and link it from your emails instead of attaching it — hosted links are not multiplied by recipient count. Optional `expires_at` form field (ISO 8601) auto-expires the file. PAID PLANS ONLY (Pay-as-you-go counts as paid) AND requires a verified sending domain with the branded tracking CNAME (track.yourdomain.com) active — hosted files are served from YOUR domain, so link reputation is yours and aligns with your From domain. Included storage starts at 100 MB; storage add-ons raise it to 10 GB or 100 GB.","security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Upload created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Upload"},"example":{"id":"up_k3XanQ9pL2mWv8Rt","filename":"catalog-2026.pdf","content_type":"application/pdf","size_bytes":1846210,"url":"https://track.acme.io/f/up_k3XanQ9pL2mWv8Rt/catalog-2026.pdf","download_count":0,"expires_at":"2026-09-30T23:59:59.000Z","created_at":"2026-08-28T10:15:00.000Z"}}}},"400":{"description":"Validation error (blocked file type, quota exceeded, empty file)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Hosted uploads require a paid plan (PAYG counts) and a verified domain with the branded tracking CNAME active","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"File exceeds the 25 MB per-file limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"Uploads not configured on this deployment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"File to upload — field name must be `file`. Maximum 25 MB per file."},"expires_at":{"type":"string","format":"date-time","description":"Optional ISO 8601 expiry. Omit the field for an upload that never expires."}}},"example":{"file":"catalog-2026.pdf (binary file content)","expires_at":"2026-09-30T23:59:59Z"}}}}},"get":{"operationId":"listUploads","summary":"List hosted uploads","tags":["Uploads"],"description":"List your hosted uploads with storage usage against your plan quota. Returns the 200 most recent uploads — this window is intentional; older uploads are not listable via the API (manage them in the Console).","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Uploads and quota usage","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadListPage"},"example":{"items":[{"id":"up_k3XanQ9pL2mWv8Rt","filename":"catalog-2026.pdf","content_type":"application/pdf","size_bytes":1846210,"url":"https://track.acme.io/f/up_k3XanQ9pL2mWv8Rt/catalog-2026.pdf","download_count":12,"expires_at":null,"created_at":"2026-08-28T10:15:00.000Z"}],"used_bytes":1846210,"quota_bytes":104857600}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"Uploads not configured on this deployment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/uploads/{id}":{"get":{"operationId":"getUpload","summary":"Get a hosted upload","tags":["Uploads"],"description":"Retrieve a hosted upload by id, including its public download URL and download count.","parameters":[{"schema":{"type":"string","pattern":"^up_[A-Za-z0-9_-]+$"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Upload details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Upload"},"example":{"id":"up_k3XanQ9pL2mWv8Rt","filename":"catalog-2026.pdf","content_type":"application/pdf","size_bytes":1846210,"url":"https://track.acme.io/f/up_k3XanQ9pL2mWv8Rt/catalog-2026.pdf","download_count":12,"expires_at":null,"created_at":"2026-08-28T10:15:00.000Z"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Upload not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"Uploads not configured on this deployment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"operationId":"updateUpload","summary":"Update upload expiry","tags":["Uploads"],"description":"Set a new expiry for a hosted upload (ISO 8601, in the future) or pass null to make it never expire. Extending works while the expired file is still in storage; once the hourly cleanup has removed an expired file, this answers 409 — upload the file again instead.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"expires_at":{"type":"string","format":"date-time","nullable":true,"description":"New expiry as an ISO 8601 timestamp (must be in the future), or null to never expire."}},"required":["expires_at"],"additionalProperties":false},"example":{"expires_at":"2027-01-01T09:00:00Z"}}}},"parameters":[{"schema":{"type":"string","pattern":"^up_[A-Za-z0-9_-]+$"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Updated upload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Upload"},"example":{"id":"up_k3XanQ9pL2mWv8Rt","filename":"catalog-2026.pdf","content_type":"application/pdf","size_bytes":1846210,"url":"https://track.acme.io/f/up_k3XanQ9pL2mWv8Rt/catalog-2026.pdf","download_count":12,"expires_at":"2027-01-01T09:00:00.000Z","created_at":"2026-08-28T10:15:00.000Z"}}}},"400":{"description":"expires_at not in the future","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Upload not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Expired file already removed from storage","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"Uploads not configured on this deployment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"operationId":"deleteUpload","summary":"Delete a hosted upload","tags":["Uploads"],"description":"Delete a hosted upload. The file stops being served immediately (existing links answer 410 Gone) and its bytes stop counting toward your quota.","parameters":[{"schema":{"type":"string","pattern":"^up_[A-Za-z0-9_-]+$"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"204":{"description":"Upload deleted"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Upload not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"Uploads not configured on this deployment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/uploads/{id}/download":{"get":{"operationId":"downloadUpload","summary":"Get a non-counting download URL","tags":["Uploads"],"description":"Return a short-lived download URL for a hosted upload that streams the file WITHOUT incrementing its download count — use this for owner-side downloads (e.g. the console). The public /f/ link stays the counting path for email recipients.","parameters":[{"schema":{"type":"string","pattern":"^up_[A-Za-z0-9_-]+$"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Non-counting download URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadDownloadLink"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Upload not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"Uploads not configured on this deployment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/uploads/{id}/clicks.csv":{"get":{"operationId":"exportUploadClicks","summary":"Export upload click report (CSV)","tags":["Uploads"],"description":"Download a CSV of this hosted upload’s activity — one row per email click (recipient, IP, country, user agent, and the source email from/to/subject — message bodies and cc/bcc are deliberately excluded from this export) and one per download of the file itself. Downloads that came through a tracked email click are `email_download` rows with the full email context (recipient, from/to/subject…); direct fetches (pasted URL) are `direct_download` rows whose email columns are empty because no email was involved; owner console downloads appear as `console_download`. Returns just the header row when there is no activity yet. Pass `excel=1` when the file is destined for Microsoft Excel: it prepends a UTF-8 BOM and an Excel `sep=,` delimiter hint so Excel parses commas correctly on every locale (many European locales default to semicolons) — omit it for programmatic consumers, who get pure RFC 4180 CSV. This synchronous endpoint returns at most 50,000 click rows plus 50,000 download rows (oldest first); when it truncates it sets the `X-Wemail-Clicks-Truncated: 1` response header. For files with more activity than that, generate the complete report as a background job instead (the console does this automatically).","parameters":[{"schema":{"type":"string","enum":["1","true","0","false"]},"in":"query","name":"excel","required":false,"description":"Set to 1 for an Excel-friendly file: UTF-8 BOM + a `sep=,` first line so Excel splits on commas regardless of locale. Omit for pure RFC 4180 CSV."},{"schema":{"type":"string","pattern":"^up_[A-Za-z0-9_-]+$"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"CSV activity report (streamed as a file attachment): email clicks and direct downloads of the hosted file, one row each with a `source` column.","headers":{"content-disposition":{"description":"attachment; filename=\"<filename>-clicks.csv\"","schema":{"type":"string"}}},"content":{"text/csv":{"schema":{"type":"string","format":"binary"},"example":"at,source,recipient,ip,country,user_agent,message_id,from,to,subject\r\n2026-08-28T10:15:00.000Z,email_click,john@example.com,203.0.113.7,Greece,Mozilla/5.0,msg_a1b2c3,\"Acme <hello@acme.io>\",john@example.com,Welcome to Acme\r\n2026-08-28T10:15:04.000Z,email_download,john@example.com,203.0.113.7,Greece,Mozilla/5.0,msg_a1b2c3,\"Acme <hello@acme.io>\",john@example.com,Welcome to Acme\r\n2026-08-28T11:02:41.000Z,direct_download,,198.51.100.4,Germany,Mozilla/5.0,,,,\r\n"}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Upload not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"Uploads not configured on this deployment","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/validate":{"post":{"operationId":"validateEmail","summary":"Validate an email address (asynchronous)","tags":["Validation"],"description":"Queues an email validation and returns 202 with a verification_id immediately. The result is delivered to webhook_url (if supplied) and can also be fetched via GET /v3/validate/{id}.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"address":{"type":"string","minLength":1,"maxLength":320,"description":"Email address to validate"},"webhook_url":{"type":"string","format":"uri","description":"If provided, the API validates asynchronously and POSTs the signed result to this URL when ready."}},"required":["address"],"additionalProperties":false},"example":{"address":"john@example.com"}}}},"security":[{"bearerAuth":[]}],"responses":{"202":{"description":"Queued for asynchronous verification; result=\"unknown\"/sub_status=\"queued\" until it resolves. Delivered to webhook_url and pollable via GET /v3/validate/{id}","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationResult"},"example":{"verification_id":"9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d","address":"john@example.com","is_valid":false,"result":"unknown","sub_status":"queued","recommendation":"review","confidence":0,"risk":"medium","reason":"Queued for asynchronous verification","did_you_mean":null,"account":"john","domain":"example.com","mx_provider":null,"mx_record":null,"mx_found":false,"is_catch_all":false,"is_disposable":false,"is_role_address":false,"is_free_provider":false,"smtp_check":null,"domain_age_days":null,"activity":null}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Insufficient verify credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/validate/{id}":{"get":{"operationId":"getValidationResult","summary":"Fetch an async validation result","tags":["Validation"],"description":"Poll a validation submitted with a webhook_url. While still processing, result=\"unknown\" and sub_status=\"queued\".","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Verification result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationResult"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/validate/bulk":{"post":{"operationId":"createBulkVerificationJob","summary":"Create bulk verification job","tags":["Validation"],"description":"Submit a list of email addresses for bulk verification via JSON body or CSV file upload.","security":[{"bearerAuth":[]}],"responses":{"202":{"description":"Bulk job created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkVerificationJobAccepted"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"Insufficient verify credits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"CSV or newline-separated list of email addresses (max 100 MB)."}}}}}}}},"/validate/evaluate":{"post":{"operationId":"evaluateEmailList","summary":"Evaluate email list quality","tags":["Validation"],"description":"Assess the quality of an email list without performing DNS/SMTP verification. Returns syntax errors, disposable/free/role percentages, and a quality score.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List evaluation result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailListEvaluation"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}},"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"CSV or newline-separated list of email addresses (max 100 MB)."}}}}}}}},"/validate/bulk/{jobId}":{"get":{"operationId":"getBulkVerificationJob","summary":"Get bulk job status","tags":["Validation"],"description":"Retrieve the status and summary of a bulk verification job.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"jobId","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Job status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkVerificationJob"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"delete":{"operationId":"deleteBulkVerificationJob","summary":"Delete a bulk verification job","tags":["Validation"],"description":"Delete a bulk verification job and its results.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"jobId","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Job deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteConfirmation"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/validate/bulk/{jobId}/results":{"get":{"operationId":"getBulkVerificationResults","summary":"Get bulk job results","tags":["Validation"],"description":"Retrieve paginated verification results for a bulk job.","parameters":[{"schema":{"type":"integer","minimum":1,"default":1},"in":"query","name":"page","required":false},{"schema":{"type":"integer","minimum":1,"maximum":1000,"default":100},"in":"query","name":"limit","required":false},{"schema":{"type":"string","enum":["valid","invalid","catch_all","unknown","spamtrap","abuse","do_not_mail"]},"in":"query","name":"result","required":false},{"schema":{"type":"string","format":"uuid"},"in":"path","name":"jobId","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Paginated results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkVerificationResultsPage"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/validate/bulk/{jobId}/download":{"get":{"operationId":"downloadBulkVerificationResults","summary":"Download bulk job results as CSV","tags":["Validation"],"description":"Download verification results as a CSV file, optionally filtered by segment.","parameters":[{"schema":{"type":"string","enum":["all","valid","invalid","catch_all","unknown","risky"],"default":"all"},"in":"query","name":"segment","required":false},{"schema":{"type":"string","format":"uuid"},"in":"path","name":"jobId","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"CSV verification results (streamed as a file attachment)","headers":{"content-disposition":{"description":"attachment; filename=\"<job>_results.csv\"","schema":{"type":"string"}}},"content":{"text/csv":{"schema":{"type":"string","format":"binary"},"example":"email,result,sub_status,confidence\njohn@example.com,valid,none,0.98\n"}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/validate/rules":{"get":{"operationId":"listVerificationRules","summary":"List verification rules","tags":["Validation"],"description":"List all custom verification rules for the account.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of rules","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationRuleList"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"operationId":"createVerificationRule","summary":"Create a verification rule","tags":["Validation"],"description":"Create a custom allow/block rule for email verification.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rule_type":{"type":"string","enum":["email","domain","tld","mx_provider"]},"value":{"type":"string","minLength":1,"maxLength":320},"action":{"type":"string","enum":["allow","block"]},"reason":{"type":"string","maxLength":500}},"required":["rule_type","value","action"],"additionalProperties":false},"example":{"rule_type":"domain","value":"example.com","action":"allow","reason":"Trusted partner domain"}}}},"security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Rule created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationRule"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/validate/rules/{ruleId}":{"delete":{"operationId":"deleteVerificationRule","summary":"Delete a verification rule","tags":["Validation"],"description":"Delete a custom verification rule.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"ruleId","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Rule deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteConfirmation"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Rule not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/subaccounts":{"get":{"operationId":"listSubaccounts","summary":"List subaccounts","tags":["Subaccounts"],"description":"Retrieve a paginated list of subaccounts.","parameters":[{"schema":{"type":"integer","minimum":1,"default":1},"in":"query","name":"page","required":false},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"in":"query","name":"per_page","required":false}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Paginated subaccount list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubaccountListPage"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"operationId":"createSubaccount","summary":"Create a subaccount","tags":["Subaccounts"],"description":"Create a new subaccount under the current account.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"slug":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[a-z0-9-]+$"}},"required":["name","slug"],"additionalProperties":false},"example":{"name":"Acme EU","slug":"acme-eu"}}}},"security":[{"bearerAuth":[]}],"responses":{"201":{"description":"Subaccount created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subaccount"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/subaccounts/{id}":{"put":{"operationId":"updateSubaccount","summary":"Update a subaccount","tags":["Subaccounts"],"description":"Update an existing subaccount.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"status":{"type":"string","enum":["active","suspended"]}},"additionalProperties":false},"example":{"name":"Acme EU","status":"active"}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Subaccount updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subaccount"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Subaccount not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/audit":{"get":{"operationId":"listAuditEntries","summary":"List audit log entries","tags":["Audit"],"description":"Retrieve a paginated list of audit log entries with optional filters.","parameters":[{"schema":{"type":"integer","minimum":1,"default":1},"in":"query","name":"page","required":false},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"in":"query","name":"per_page","required":false},{"schema":{"type":"string"},"in":"query","name":"action","required":false},{"schema":{"type":"string","enum":["user","staff","system"]},"in":"query","name":"actor_type","required":false},{"schema":{"type":"string","format":"date-time"},"in":"query","name":"since","required":false},{"schema":{"type":"string","format":"date-time"},"in":"query","name":"until","required":false}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Paginated audit log","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditLogPage"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/metrics":{"get":{"operationId":"getMetrics","summary":"Query delivery metrics","tags":["Metrics"],"description":"Retrieve aggregated email delivery metrics as a gap-filled, timezone-aware time series with optional grouping.","parameters":[{"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}]},"in":"query","name":"since","required":false,"description":"Start of time range — ISO 8601 instant or calendar day YYYY-MM-DD (day = 00:00 in `timezone`). Default: 30 days ago."},{"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}]},"in":"query","name":"until","required":false,"description":"End of time range — ISO 8601 instant or calendar day YYYY-MM-DD (day = end of that day in `timezone`, inclusive). Default: now."},{"schema":{"type":"string","enum":["hour","day","week","month"],"default":"day"},"in":"query","name":"resolution","required":false},{"schema":{"type":"string","maxLength":64,"default":"UTC"},"in":"query","name":"timezone","required":false,"description":"IANA timezone for time bucketing (default UTC)"},{"schema":{"type":"string","enum":["domain","status"]},"in":"query","name":"group_by","required":false},{"schema":{"type":"string","maxLength":253},"in":"query","name":"domain","required":false,"description":"Filter by sending domain (from-address domain)"},{"schema":{"type":"string","maxLength":253},"in":"query","name":"recipient_domain","required":false,"description":"Filter by recipient mailbox domain"},{"schema":{"type":"string","maxLength":128},"in":"query","name":"tag","required":false,"description":"Filter by message/event tag"},{"schema":{"type":"string","maxLength":64},"in":"query","name":"template","required":false,"description":"Filter by template ID"}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Delivery metrics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricsTimeseries"},"example":{"data_as_of":"2026-08-29T12:00:00.000Z","data":[{"timestamp":"2026-08-14T00:00:00.000Z","sent":1240,"delivered":1195,"bounced":18,"complained":1,"opened":642,"clicked":187,"unsubscribed":2,"delayed":6,"rejected":0,"failed":5},{"timestamp":"2026-08-15T00:00:00.000Z","sent":1315,"delivered":1276,"bounced":12,"complained":0,"opened":705,"clicked":214,"unsubscribed":1,"delayed":3,"rejected":1,"failed":3},{"timestamp":"2026-08-16T00:00:00.000Z","sent":980,"delivered":951,"bounced":9,"complained":0,"opened":512,"clicked":149,"unsubscribed":0,"delayed":2,"rejected":0,"failed":2}],"resolution":"day","timezone":"UTC","period":{"since":"2026-08-14T00:00:00.000Z","until":"2026-08-16T23:59:59.000Z"},"group_by":null}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/metrics/summary":{"get":{"operationId":"getMetricsSummary","summary":"Get a delivery metrics summary","tags":["Metrics"],"description":"Single aggregate over the window: lifecycle counts, derived rates, bounce classification, engagement (including unique opens/clicks), latency percentiles, and an optional previous-period comparison.","parameters":[{"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}]},"in":"query","name":"since","required":false,"description":"Start of time range — ISO 8601 instant or calendar day YYYY-MM-DD (day = 00:00 in `timezone`). Default: 30 days ago."},{"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}]},"in":"query","name":"until","required":false,"description":"End of time range — ISO 8601 instant or calendar day YYYY-MM-DD (day = end of that day in `timezone`, inclusive). Default: now."},{"schema":{"type":"string","maxLength":64,"default":"UTC"},"in":"query","name":"timezone","required":false,"description":"IANA timezone for time bucketing (default UTC)"},{"schema":{"type":"string","enum":["previous_period"]},"in":"query","name":"compare","required":false,"description":"Include the immediately preceding window of equal length plus deltas"},{"schema":{"type":"string","maxLength":253},"in":"query","name":"domain","required":false,"description":"Filter by sending domain (from-address domain)"},{"schema":{"type":"string","maxLength":253},"in":"query","name":"recipient_domain","required":false,"description":"Filter by recipient mailbox domain"},{"schema":{"type":"string","maxLength":128},"in":"query","name":"tag","required":false,"description":"Filter by message/event tag"},{"schema":{"type":"string","maxLength":64},"in":"query","name":"template","required":false,"description":"Filter by template ID"}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Aggregated delivery, engagement and latency summary","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricsSummary"},"example":{"data_as_of":"2026-08-29T12:00:00.000Z","period":{"since":"2026-07-17T00:00:00.000Z","until":"2026-08-16T23:59:59.000Z"},"delivery":{"sent":35480,"delivered":34210,"bounced":412,"bounces":{"hard":268,"soft":121,"suppressed":14,"undetermined":9,"hard_rate":0.0076,"soft_rate":0.0034,"suppressed_rate":0.0004,"undetermined_rate":0.0003},"complained":11,"delayed":187,"rejected":4,"failed":96,"delivery_rate":0.9642,"bounce_rate":0.0116,"complaint_rate":0.0003},"engagement":{"opens":18420,"opens_direct":12960,"unique_opens":11875,"clicks":5230,"unique_clicks":3411,"unsubscribes":42,"open_rate":0.3471,"click_rate":0.0997,"unsubscribe_rate":0.0012},"latency":{"processing":{"p50_ms":240,"p95_ms":890,"p99_ms":1720},"delivery":{"p50_ms":1120,"p95_ms":4530,"p99_ms":11840},"total":{"p50_ms":1390,"p95_ms":5210,"p99_ms":13100}},"timezone":"UTC"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/metrics/breakdown":{"get":{"operationId":"getMetricsBreakdown","summary":"Break metrics down by a dimension","tags":["Metrics"],"description":"Ranked table of delivery/engagement metrics for one dimension: sending or recipient domain, mailbox provider, tag, template, bounce code, complaint type, country, or email client. Optionally includes a per-day trend series per row.","parameters":[{"schema":{"type":"string","enum":["sending_domain","recipient_domain","mailbox_provider","tag","template","bounce_code","complaint_type","country","client"]},"in":"query","name":"dimension","required":true,"description":"Dimension to break metrics down by"},{"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}]},"in":"query","name":"since","required":false,"description":"Start of time range — ISO 8601 instant or calendar day YYYY-MM-DD (day = 00:00 in `timezone`). Default: 30 days ago."},{"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}]},"in":"query","name":"until","required":false,"description":"End of time range — ISO 8601 instant or calendar day YYYY-MM-DD (day = end of that day in `timezone`, inclusive). Default: now."},{"schema":{"type":"string","maxLength":64,"default":"UTC"},"in":"query","name":"timezone","required":false,"description":"IANA timezone for time bucketing (default UTC)"},{"schema":{"type":"string","enum":["sent","delivered","bounced","complained","opened","clicked","unique_opens","unique_clicks","failed"]},"in":"query","name":"sort","required":false,"description":"Metric to rank by (default depends on dimension)"},{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"string","enum":["true","false"],"default":"false"},"in":"query","name":"include_trend","required":false,"description":"Include a per-day series of the sort metric for each row"}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Ranked metrics broken down by one dimension","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetricsBreakdown"},"example":{"data_as_of":"2026-08-29T12:00:00.000Z","dimension":"mailbox_provider","period":{"since":"2026-07-17T00:00:00.000Z","until":"2026-08-16T23:59:59.000Z"},"timezone":"UTC","sort":"delivered","total":7,"data":[{"key":"Gmail","label":null,"sent":14210,"delivered":13890,"bounced":96,"complained":4,"opened":8340,"clicked":2410,"unique_opens":5210,"unique_clicks":1622,"failed":31,"delivery_rate":0.9775,"bounce_rate":0.0068,"complaint_rate":0.0003,"open_rate":0.3751,"click_rate":0.1168,"trend":[{"timestamp":"2026-08-14T00:00:00.000Z","value":455},{"timestamp":"2026-08-15T00:00:00.000Z","value":471},{"timestamp":"2026-08-16T00:00:00.000Z","value":402}]},{"key":"Microsoft","label":null,"sent":9105,"delivered":8710,"bounced":187,"complained":5,"opened":4120,"clicked":1180,"unique_opens":2960,"unique_clicks":803,"failed":22,"delivery_rate":0.9566,"bounce_rate":0.0205,"complaint_rate":0.0005,"open_rate":0.3398,"click_rate":0.0922,"trend":[{"timestamp":"2026-08-14T00:00:00.000Z","value":288},{"timestamp":"2026-08-15T00:00:00.000Z","value":301},{"timestamp":"2026-08-16T00:00:00.000Z","value":259}]}]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/whoami":{"get":{"operationId":"whoami","summary":"Identify the authenticated caller","tags":["Utility"],"description":"Returns the account and API key details associated with the current authentication credentials.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Authenticated caller identity","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Identity"},"example":{"account":{"id":"3f6b2a9e-1c4d-4e8f-9a0b-5c6d7e8f9a0b","name":"Acme Corp","plan":"professional","status":"active"},"api_key":{"id":"7e6d5c4b-3a29-4180-9f8e-7d6c5b4a3928","scope":"full_access","environment":"live"}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/usage":{"get":{"operationId":"getUsage","summary":"Current quota, usage and credits","tags":["Utility"],"description":"Returns the workspace's monthly email allowance and how much of it is used, the daily throttle, the verification-credit balance, and the account rate limit — the same numbers the platform enforces, so integrations can check headroom before sending instead of discovering a 429.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Current usage and quota","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Usage"},"example":{"plan":"professional","quota_source":"own_plan","period":{"start":"2026-08-01T00:00:00.000Z","end":"2026-09-01T00:00:00.000Z"},"emails":{"limit":50000,"used":12480,"remaining":37520,"ceiling":55000,"overage_enabled":true},"daily":{"limit":5000,"used":480,"remaining":4520},"verify_credits":{"balance":2500},"rate_limit_per_minute":null}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/pools":{"get":{"operationId":"listIpPools","summary":"List IP pools","tags":["IP Pools"],"description":"Retrieve a paginated list of IP pools.","parameters":[{"schema":{"type":"integer","minimum":1,"default":1},"in":"query","name":"page","required":false},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"in":"query","name":"per_page","required":false}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Paginated IP pool list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IpPoolListPage"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"operationId":"createIpPool","summary":"Create an IP pool","tags":["IP Pools"],"description":"Create a new IP pool for dedicated sending.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"provider":{"type":"string","enum":["cloud-relay","apifon-postal"]},"region":{"type":"string","maxLength":50},"type":{"type":"string","enum":["dedicated","shared","warming"],"default":"shared"},"ips":{"type":"array","items":{"type":"string"},"maxItems":256,"default":[]}},"required":["name","provider"],"additionalProperties":false},"example":{"name":"Transactional pool","provider":"cloud-relay","region":"eu-west-1","type":"dedicated","ips":["203.0.113.10"]}}}},"security":[{"bearerAuth":[]}],"responses":{"201":{"description":"IP pool created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IpPool"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Plan does not include dedicated IPs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/pools/{id}":{"put":{"operationId":"updateIpPool","summary":"Update an IP pool","tags":["IP Pools"],"description":"Update an existing IP pool configuration.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"region":{"type":"string","maxLength":50},"type":{"type":"string","enum":["dedicated","shared","warming"]},"ips":{"type":"array","items":{"type":"string"},"maxItems":256}},"additionalProperties":false},"example":{"name":"Transactional pool","type":"warming"}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"IP pool updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IpPool"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Plan does not include dedicated IPs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"IP pool not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/ips":{"get":{"operationId":"listIps","summary":"List dedicated IPs","tags":["IPs"],"description":"Retrieve a paginated list of dedicated IP addresses.","parameters":[{"schema":{"type":"integer","minimum":1,"default":1},"in":"query","name":"page","required":false},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"in":"query","name":"per_page","required":false},{"schema":{"type":"string","format":"uuid"},"in":"query","name":"pool_id","required":false}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Paginated IP list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IpListPage"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/verify":{"post":{"operationId":"verifyProofOfSending","summary":"Verify a Proof-of-Sending certificate","tags":["Proof of Sending"],"description":"Public, no account required. Looks up a certificate by its Document ID (pod_…) and returns only integrity facts — never recipient PII. Verified certificates report whether the signature and content fingerprint are intact, the registry type, and the issue timestamp.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"certificate":{"type":"string","description":"The Proof-of-Sending certificate / Document ID (pod_…)."}},"required":["certificate"],"additionalProperties":false},"example":{"certificate":"eyJ2IjoxLCJhbGciOiJFZDI1NTE5In0.eyJtIjoibXNnX2ExYjJjMyIsInMiOiJoZWxsb0BhY21lLmlvIn0.c2lnbmF0dXJl"}}}},"security":[],"responses":{"200":{"description":"Verification result — integrity facts only, no PII","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProofVerificationResult"}}}},"400":{"description":"Malformed certificate id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}}},"servers":[{"url":"https://api.wemail.io/v3","description":"Production"},{"url":"/v3"}],"tags":[{"name":"Messages","description":"Send transactional email and read message status"},{"name":"Events","description":"Delivery, open, click, bounce and complaint events"},{"name":"Domains","description":"Sending domains, DNS verification and branded tracking"},{"name":"Templates","description":"Reusable email templates with variables"},{"name":"Uploads","description":"Hosted file uploads linked from emails — paid plans with an active branded tracking domain"},{"name":"Suppressions","description":"Bounce, complaint and unsubscribe lists, plus the always-deliver allowlist"},{"name":"Data removals","description":"GDPR data-removal (right to erasure) requests and their status"},{"name":"Validation","description":"Single and bulk email address verification"},{"name":"Webhooks","description":"HTTPS callbacks for message events"},{"name":"API Keys","description":"Create, scope, rotate and revoke API keys"},{"name":"Subaccounts","description":"Isolated workspaces under one organisation"},{"name":"Lists","description":"Mailing lists and members"},{"name":"Audit","description":"Account audit trail"},{"name":"Metrics","description":"Aggregated sending and engagement metrics"},{"name":"IP Pools","description":"Dedicated IP pool management"},{"name":"IPs","description":"Dedicated IP management and warm-up"},{"name":"Proof of Sending","description":"Verify Proof-of-Sending certificates (public, no auth)"},{"name":"Utility","description":"Usage, ping and account helpers"}],"externalDocs":{"description":"wemail API guides and reference","url":"https://wemail.io/docs"}}