openapi: 3.2.0 info: version: 2.0.0 x-latency-category: responsive x-endpoint-cost: light title: Telnyx Email Suppressions API description: SIP trunking, SMS, MMS, Call Control and Telephony Data Services. contact: email: support@telnyx.com servers: - url: https://api.telnyx.com/v2 description: Version 2.0.0 of the Telnyx API security: - bearerAuth: [] tags: - name: Email Suppressions description: Recipient suppression records (`/v2/email_blocks`). paths: /email_blocks: get: tags: - Email Suppressions summary: List suppressions description: 'Account-scoped list. Two mutually exclusive pagination modes: - **Offset**: `page[number]` (default 1) + `page[size]` (default 25, max 100). `meta` contains `total_pages`. - **Cursor**: `page[after]` and/or `page[before]` (opaque `Base.url_encode64` of `{"created_at","id"}`). Cannot combine with `page[number]`; `after`+`before` together is an error. `meta` contains `next_cursor` / `previous_cursor` (omitted when their flag is false). Sort defaults to `-created_at` (desc); only `created_at` is sortable. A `--` prefix is an error. `nil`/empty filter values are silently dropped.' operationId: listEmailBlocks parameters: - $ref: '#/components/parameters/BlocksPageNumber' - $ref: '#/components/parameters/BlocksPageSize' - $ref: '#/components/parameters/BlocksPageAfter' - $ref: '#/components/parameters/BlocksPageBefore' - $ref: '#/components/parameters/SortEmailBlocks' - $ref: '#/components/parameters/FilterReason' - $ref: '#/components/parameters/FilterDomainId' - $ref: '#/components/parameters/FilterCreatedAfter' - $ref: '#/components/parameters/FilterCreatedBefore' responses: '200': description: List of suppressions. content: application/json: schema: oneOf: - $ref: '#/components/schemas/EmailBlockListOffsetResponse' - $ref: '#/components/schemas/EmailBlockListCursorResponse' example: data: - id: 3fa85f64-5717-4562-b3fc-2c963f66afa6 record_type: email_block domain_id: 3fa85f64-5717-4562-b3fc-2c963f66afa6 group_id: 3fa85f64-5717-4562-b3fc-2c963f66afa6 from: string to: string reason: hard_bounce source: feedback scope: account status: active created_at: '2024-01-23T18:10:02.574Z' updated_at: '2024-01-23T18:10:02.574Z' expires_at: '2024-01-23T18:10:02.574Z' meta: page_number: 0 page_size: 0 total_pages: 0 total_results: 0 '400': $ref: '#/components/responses/ValidationErrorQuery' '401': $ref: '#/components/responses/email_Unauthorized' '406': $ref: '#/components/responses/FrameworkError' post: tags: - Email Suppressions summary: Create a manual suppression description: 'Creates a suppression with `reason: manual_block` and `source: manual`. Caller-supplied `reason` / `source` are **ignored**; `scope` is **derived** server-side from `domain_id` / `from` and is never trusted. Idempotent: if a matching row already exists (NULL-safe dedupe key: account_id, scope, to, reason, domain_id, from), returns the existing record with `200` (no new audit event). `bounce_category`, `dsn_code`, `meta`, and `group_id` are **not accepted** on the public surface. Use the unsubscribe-group suppression endpoint or the internal create surface for those.' operationId: createEmailBlock requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateEmailBlockRequest' example: to: spammer@bad.tld expires_at: '2026-12-31T23:59:59Z' responses: '200': description: Idempotent — matching suppression already existed. content: application/json: schema: $ref: '#/components/schemas/EmailBlockResponse' example: data: id: 3fa85f64-5717-4562-b3fc-2c963f66afa6 record_type: email_block domain_id: 3fa85f64-5717-4562-b3fc-2c963f66afa6 group_id: 3fa85f64-5717-4562-b3fc-2c963f66afa6 from: string to: string reason: hard_bounce source: feedback scope: account status: active created_at: '2024-01-23T18:10:02.574Z' updated_at: '2024-01-23T18:10:02.574Z' expires_at: '2024-01-23T18:10:02.574Z' '201': description: Created. content: application/json: schema: $ref: '#/components/schemas/EmailBlockResponse' example: data: id: 3fa85f64-5717-4562-b3fc-2c963f66afa6 record_type: email_block domain_id: 3fa85f64-5717-4562-b3fc-2c963f66afa6 group_id: 3fa85f64-5717-4562-b3fc-2c963f66afa6 from: string to: string reason: hard_bounce source: feedback scope: account status: active created_at: '2024-01-23T18:10:02.574Z' updated_at: '2024-01-23T18:10:02.574Z' expires_at: '2024-01-23T18:10:02.574Z' '401': $ref: '#/components/responses/email_Unauthorized' '406': $ref: '#/components/responses/FrameworkError' '422': $ref: '#/components/responses/ChangesetError' /email_blocks/export: get: tags: - Email Suppressions summary: Export suppressions as CSV description: 'Streams the account''s suppressions as a chunked CSV (server-side cursor; never materialized). Content-type `text/csv`, header `Content-Disposition: attachment; filename="email_blocks_export.csv"`. Filters (`filter[reason]`, `filter[domain_id]`, `filter[created_after]`, `filter[created_before]`) are the only params that affect output. `sort` and `page[*]` are **parsed** (bad values still produce `400`) but **ignored** — rows stream `ORDER BY created_at ASC, id ASC` with no pagination. CSV columns: `id,to,from,reason,source,scope,status,domain_id, created_at,updated_at,expires_at,group_id,bounce_category,dsn_code, meta` (15 columns). The first 12 columns are the stable native signature; `bounce_category`, `dsn_code`, and `meta` are optional backup fields (empty when unset). The CSV carries the `group_id` column so group-scoped suppressions'' group link survives the export (empty for account-scope rows).' operationId: exportEmailBlocks parameters: - $ref: '#/components/parameters/SortEmailBlocks' - $ref: '#/components/parameters/BlocksPageNumber' - $ref: '#/components/parameters/BlocksPageSize' - $ref: '#/components/parameters/FilterReason' - $ref: '#/components/parameters/FilterDomainId' - $ref: '#/components/parameters/FilterCreatedAfter' - $ref: '#/components/parameters/FilterCreatedBefore' responses: '200': description: CSV stream. content: text/csv: schema: type: string description: CSV with header row `id,to,from,reason,source,scope,status,domain_id,created_at,updated_at,expires_at,group_id,bounce_category,dsn_code,meta`. example: string '400': $ref: '#/components/responses/ValidationErrorQuery' '401': $ref: '#/components/responses/email_Unauthorized' '406': $ref: '#/components/responses/FrameworkError' /email_blocks/{id}: parameters: - $ref: '#/components/parameters/PathId' delete: tags: - Email Suppressions summary: Soft-delete a suppression description: 'Soft-deletes (status → `removed`; tombstone retained). A `removed` audit event is appended unless the block was already `removed` (idempotent — returns the existing row with `200` and no new event). Mutates `updated_at`.' operationId: deleteEmailBlock responses: '200': description: The post-deletion record (status `removed`). content: application/json: schema: $ref: '#/components/schemas/EmailBlockResponse' example: data: id: 3fa85f64-5717-4562-b3fc-2c963f66afa6 record_type: email_block domain_id: 3fa85f64-5717-4562-b3fc-2c963f66afa6 group_id: 3fa85f64-5717-4562-b3fc-2c963f66afa6 from: string to: string reason: hard_bounce source: feedback scope: account status: active created_at: '2024-01-23T18:10:02.574Z' updated_at: '2024-01-23T18:10:02.574Z' expires_at: '2024-01-23T18:10:02.574Z' '401': $ref: '#/components/responses/email_Unauthorized' '404': $ref: '#/components/responses/BlocksNotFound' '406': $ref: '#/components/responses/FrameworkError' '422': $ref: '#/components/responses/ChangesetError' get: tags: - Email Suppressions summary: Retrieve a suppression operationId: showEmailBlock responses: '200': description: The suppression. content: application/json: schema: $ref: '#/components/schemas/EmailBlockResponse' example: data: id: 3fa85f64-5717-4562-b3fc-2c963f66afa6 record_type: email_block domain_id: 3fa85f64-5717-4562-b3fc-2c963f66afa6 group_id: 3fa85f64-5717-4562-b3fc-2c963f66afa6 from: string to: string reason: hard_bounce source: feedback scope: account status: active created_at: '2024-01-23T18:10:02.574Z' updated_at: '2024-01-23T18:10:02.574Z' expires_at: '2024-01-23T18:10:02.574Z' '401': $ref: '#/components/responses/email_Unauthorized' '404': $ref: '#/components/responses/BlocksNotFound' '406': $ref: '#/components/responses/FrameworkError' description: Returns the account-owned suppression identified by ID. Cross-account lookups and malformed IDs return `404` without exposing another account’s data. /email_blocks/{id}/events: parameters: - $ref: '#/components/parameters/PathId' get: tags: - Email Suppressions summary: List audit events for a suppression description: 'Offset pagination only (`page[number]` default 1, `page[size]` default **50**, max 100). No `sort`, no `filter`, no cursor — ordering is fixed `desc occurred_at, desc id`. Verifies the block belongs to the account first (cross-account → 404).' operationId: listEmailBlockEvents parameters: - $ref: '#/components/parameters/BlocksPageNumber' - name: page[size] in: query description: Page size (default 50, max 100). schema: type: integer minimum: 1 maximum: 100 default: 50 responses: '200': description: Audit events (newest-first). content: application/json: schema: $ref: '#/components/schemas/EmailBlockEventListResponse' example: data: - id: 3fa85f64-5717-4562-b3fc-2c963f66afa6 record_type: email_block_event event_type: created reason: string source: string actor: string occurred_at: '2024-01-23T18:10:02.574Z' meta: page_number: 0 page_size: 0 total_pages: 0 total_results: 0 '400': $ref: '#/components/responses/ValidationErrorQuery' '401': $ref: '#/components/responses/email_Unauthorized' '404': $ref: '#/components/responses/BlocksNotFound' '406': $ref: '#/components/responses/FrameworkError' components: parameters: FilterCreatedBefore: name: filter[created_before] in: query description: '`created_at < value` (ISO 8601).' schema: type: string format: date-time BlocksPageNumber: name: page[number] in: query description: Offset page number (≥1, default 1). schema: type: integer minimum: 1 default: 1 BlocksPageBefore: name: page[before] in: query description: Opaque cursor (see `page[after]`). Mutually exclusive with `page[after]` and `page[number]`. schema: type: string PathId: name: id in: path required: true description: Resource UUID. Malformed UUIDs are treated as not-found (not 400). schema: type: string format: uuid BlocksPageSize: name: page[size] in: query description: Page size (1–100, default 25). schema: type: integer minimum: 1 maximum: 100 default: 25 SortEmailBlocks: name: sort in: query description: Sort field. Leading `-` = desc; only `created_at` is sortable. Default `-created_at`. `--` is an error. schema: type: string default: -created_at enum: - created_at - -created_at FilterCreatedAfter: name: filter[created_after] in: query description: '`created_at > value` (ISO 8601).' schema: type: string format: date-time FilterDomainId: name: filter[domain_id] in: query description: Exact-match filter on domain_id (UUID). schema: type: string format: uuid BlocksPageAfter: name: page[after] in: query description: Opaque cursor (`Base.url_encode64` of `{"created_at","id"}`). Cursor mode; mutually exclusive with `page[number]` and `page[before]`. schema: type: string FilterReason: name: filter[reason] in: query description: Exact-match filter on reason. schema: $ref: '#/components/schemas/Reason' responses: BlocksNotFound: description: Resource not found (cross-account lookups and malformed UUIDs also return 404 — no leak). content: application/json: schema: $ref: '#/components/schemas/ErrorList' example: errors: - code: '10001' title: Not Found detail: The requested email block was not found FrameworkError: description: 'Framework-rendered error (e.g. 406 Not Acceptable, 405 Method Not Allowed, 415 Unsupported Media Type). HTTP status matches the error and the body `code` carries that same status (e.g. `"406"`, not a hardcoded `"500"`). The explicit `500.json` clause still emits `code: "500"` for genuine 500s. ' content: application/json: schema: $ref: '#/components/schemas/ErrorList' example: errors: - code: '406' title: Not Acceptable detail: Not Acceptable ChangesetError: description: Validation error (changeset or internal `Params`). One error object per field, `source.pointer /data/attributes/`. content: application/json: schema: $ref: '#/components/schemas/ErrorList' example: errors: - code: '10015' title: Validation Failed detail: can't be blank source: pointer: /data/attributes/to email_Unauthorized: description: Missing or invalid gateway auth. content: application/json: schema: $ref: '#/components/schemas/ErrorList' example: errors: - code: '10007' title: Unauthorized detail: Missing authentication ValidationErrorQuery: description: Query-param validation error (`source.pointer /`). content: application/json: schema: $ref: '#/components/schemas/ErrorList' example: errors: - code: '10015' title: Validation Failed detail: is invalid source: pointer: /filter[reason] schemas: OffsetMeta: type: object required: - page_number - page_size - total_pages - total_results properties: page_number: type: integer page_size: type: integer total_pages: type: integer total_results: type: integer EmailBlock: type: object required: - id - record_type - to - reason - source - scope - status - created_at - updated_at properties: id: type: string format: uuid record_type: type: string enum: - email_block description: View-only discriminator. domain_id: type: string format: uuid nullable: true description: '`null` ⇒ account scope. Stored on the row; exposed here.' group_id: type: string format: uuid nullable: true description: '`null` ⇒ global; set ⇒ group-scoped opt-out.' from: type: string nullable: true description: '`null` ⇒ not address-scope. (schema: from_address)' to: type: string description: 'Normalized recipient. (schema: to_address)' reason: $ref: '#/components/schemas/Reason' source: $ref: '#/components/schemas/email_Source' scope: $ref: '#/components/schemas/Scope' status: $ref: '#/components/schemas/Status' created_at: type: string format: date-time updated_at: type: string format: date-time expires_at: type: string format: date-time nullable: true description: 'Optional expiration time. An active row stops matching send-time suppression checks as soon as `expires_at <= now()`. A maintenance worker later transitions the row to `status: expired` and appends an `expired` audit event (normally within 15 minutes). ' description: 'Suppression record. Schema fields hidden by the view: `account_id`, `bounce_category`, `dsn_code`, `meta`. ' email_Source: type: string enum: - feedback - manual - import - system EmailBlockResponse: type: object required: - data properties: data: $ref: '#/components/schemas/EmailBlock' CreateEmailBlockRequest: type: object required: - to properties: to: type: string description: 'Recipient address (normalized: trim + lower-case).' from: type: string nullable: true description: Sender address (normalized). `null` ⇒ account/domain scope. domain_id: type: string format: uuid nullable: true description: '`null` ⇒ account scope.' expires_at: type: string format: date-time nullable: true description: '`reason`/`source` are forced to `manual_block`/`manual` (ignored if supplied). `scope` is derived. `bounce_category`/`dsn_code`/`meta`/ `group_id` are not accepted on the public surface. ' CursorMeta: type: object required: - page_size - has_next - has_previous properties: page_size: type: integer next_cursor: type: string description: Omitted when `has_next` is false. previous_cursor: type: string description: Omitted when `has_previous` is false. has_next: type: boolean has_previous: type: boolean Reason: type: string enum: - hard_bounce - spam_complaint - unsubscribe - invalid - manual_block EmailBlockEventListResponse: type: object required: - data - meta properties: data: type: array items: $ref: '#/components/schemas/EmailBlockEvent' meta: $ref: '#/components/schemas/OffsetMeta' EmailBlockEvent: type: object required: - id - record_type - event_type - reason - source - actor - occurred_at properties: id: type: string format: uuid record_type: type: string enum: - email_block_event description: View-only. event_type: type: string enum: - created - removed - expired - override_used reason: type: string description: Free-text snapshot of the block's reason at event time. source: type: string description: Free-text snapshot of the block's source at event time. actor: type: string description: Free-text (`user_id`/`org_id`/`api_key`/`dev_bypass`/`system`/`manual`). meta: type: object nullable: true additionalProperties: true description: '`null` when the schema field is nil (the context usually sets it to `{}`).' occurred_at: type: string format: date-time ErrorList: type: object required: - errors properties: errors: type: array items: $ref: '#/components/schemas/BlocksErrorObject' BlocksErrorObject: type: object required: - code - title - detail properties: code: type: string description: 'Error codes: `10001` (Not Found), `10007` (Unauthorized), `10015` (Validation Failed), `10019` (Internal — import create fallback), `40901` (Conflict), `500` (framework/catch-all). ' title: type: string detail: type: string source: type: object properties: pointer: type: string description: '`/data/attributes/` for changeset/Params; `/` for query-param errors.' EmailBlockListOffsetResponse: type: object required: - data - meta properties: data: type: array items: $ref: '#/components/schemas/EmailBlock' meta: $ref: '#/components/schemas/OffsetMeta' Scope: type: string enum: - account - domain - address description: Derived server-side from `domain_id`/`from`; never trusted from the caller. Status: type: string enum: - active - expired - removed EmailBlockListCursorResponse: type: object required: - data - meta properties: data: type: array items: $ref: '#/components/schemas/EmailBlock' meta: $ref: '#/components/schemas/CursorMeta' securitySchemes: BearerAuth: type: http scheme: bearer bearerFormat: API key description: 'Telnyx API key supplied as `Authorization: Bearer `. In production, auth may be validated by the API gateway and forwarded via Telnyx auth headers.' Payment: type: apiKey in: header name: Authorization description: 'Machine Payment Protocol credential used on paid retries, sent as `Authorization: Payment ...`. Obtained by paying a challenge returned in the `WWW-Authenticate` header of a 402 response. This is not a Telnyx API key; initial challenge requests use standard bearer authentication instead.' agent-memory_bearerAuth: type: http scheme: bearer description: Telnyx API key bearerAuth: type: http scheme: bearer branded-calling_bearerAuth: type: http scheme: bearer description: Telnyx API key. Generate one at https://portal.telnyx.com/#/app/api-keys. collections_bearerAuth: type: http scheme: bearer description: Telnyx API key. Collections and results are automatically scoped to the authenticated user's organization. number-reputation_bearerAuth: type: http scheme: bearer description: Telnyx API key. Generate one at https://portal.telnyx.com/#/app/api-keys. oauthClientAuth: type: oauth2 flows: clientCredentials: tokenUrl: https://api.telnyx.com/v2/oauth/token scopes: admin: Administrative access to Telnyx resources authorizationCode: authorizationUrl: https://api.telnyx.com/v2/oauth/authorize tokenUrl: https://api.telnyx.com/v2/oauth/token refreshUrl: https://api.telnyx.com/v2/oauth/token scopes: admin: Administrative access to Telnyx resources description: OAuth 2.0 authentication for Telnyx API and MCP integrations outbound-voice-profiles_bearerAuth: type: http scheme: bearer bearerFormat: JWT pronunciation-dicts_bearerAuth: type: http scheme: bearer description: Telnyx API v2 key. Obtain from https://portal.telnyx.com rcs-registration_bearerAuth: type: http scheme: bearer bearerFormat: API key stored-payment-transactions_bearerAuth: type: http scheme: bearer bearerFormat: JWT transcriptions-search_bearerAuth: type: http scheme: bearer description: Telnyx API key. Results are automatically scoped to the authenticated user's organization. web-search_bearerAuth: type: http scheme: bearer description: Telnyx API key x-service-info: categories: - communication - developer-tools docs: apiReference: https://developers.telnyx.com homepage: https://telnyx.com llms: https://telnyx.com/llms.txt