openapi: 3.0.3 info: title: 'Postmaster+ API Documentation' description: '' version: 1.0.0 servers: - url: 'https://postmasterplus.app' tags: - name: Domains description: '' - name: APRF description: '' - name: IPs description: '' - name: Intelligence description: '' - name: Blocklists description: '' - name: Seedbox description: '' - name: 'Spam Identifiers' description: '' - name: Tools description: '' components: securitySchemes: default: type: http scheme: bearer description: 'You can retrieve your token by visiting your dashboard and clicking Team Settings > API Tokens.' security: - default: [] paths: /api/v1/domains: get: summary: 'Retrieve Domains.' operationId: retrieveDomains description: '' parameters: - in: query name: page description: 'The page number. Must be at least 1.' example: 1 required: false schema: type: integer description: 'The page number. Must be at least 1.' example: 1 - in: query name: per_page description: 'The number of domains per page. Must be at least 1. Must not be greater than 100.' example: 15 required: false schema: type: integer description: 'The number of domains per page. Must be at least 1. Must not be greater than 100.' example: 15 responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Domains retrieved successfully.' data: - id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: example.com description: 'Primary sending domain' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' meta: current_page: 1 from: 1 last_page: 1 per_page: 15 to: 1 total: 1 properties: success: type: boolean example: true message: type: string example: 'Domains retrieved successfully.' data: type: array example: - id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: example.com description: 'Primary sending domain' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' items: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: type: string example: example.com description: type: string example: 'Primary sending domain' created_at: type: string example: '2026-03-28T10:30:00.000000Z' updated_at: type: string example: '2026-03-28T10:30:00.000000Z' meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 1 per_page: type: integer example: 15 to: type: integer example: 1 total: type: integer example: 1 tags: - Domains '/api/v1/domains/{id}/feed': get: summary: 'Retrieve Domain Feed.' operationId: retrieveDomainFeed description: '' parameters: - in: query name: page description: 'The page number for paginated feed results. Must be at least 1.' example: 1 required: false schema: type: integer description: 'The page number for paginated feed results. Must be at least 1.' example: 1 nullable: true - in: query name: date description: 'Filter feed items reported on or after this date, up to today. Must not be older than 90 days from today. Must be a valid date in the format Y-m-d.' example: '2026-08-06' required: false schema: type: string description: 'Filter feed items reported on or after this date, up to today. Must not be older than 90 days from today. Must be a valid date in the format Y-m-d.' example: '2026-08-06' nullable: true responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Domain feed retrieved successfully.' data: - reported_at: 'Mar 28, 2026 5:30PM' mail_stream: 'Transactional Stream' domain: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: example.com description: 'Primary sending domain' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' spf_domain: example.com seed_message: mail_from_name: 'Test Sender' mail_from: sender@example.com subject: 'Seed message' preview_line: 'This is a preview.' is_compliant: true is_blocklisted: false is_test: false - message: 'SPF is valid.' reported_at: 'Mar 28, 2026 4:45PM' mail_stream: 'Transactional Stream' domain: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: example.com description: 'Primary sending domain' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' spf_domain: null seed_message: null meta: current_page: 1 from: 1 last_page: 2 per_page: 15 to: 15 total: 16 properties: success: type: boolean example: true message: type: string example: 'Domain feed retrieved successfully.' data: type: array example: - reported_at: 'Mar 28, 2026 5:30PM' mail_stream: 'Transactional Stream' domain: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: example.com description: 'Primary sending domain' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' spf_domain: example.com seed_message: mail_from_name: 'Test Sender' mail_from: sender@example.com subject: 'Seed message' preview_line: 'This is a preview.' is_compliant: true is_blocklisted: false is_test: false - message: 'SPF is valid.' reported_at: 'Mar 28, 2026 4:45PM' mail_stream: 'Transactional Stream' domain: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: example.com description: 'Primary sending domain' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' spf_domain: null seed_message: null items: type: object properties: reported_at: type: string example: 'Mar 28, 2026 5:30PM' mail_stream: type: string example: 'Transactional Stream' domain: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: type: string example: example.com description: type: string example: 'Primary sending domain' created_at: type: string example: '2026-03-28T10:30:00.000000Z' updated_at: type: string example: '2026-03-28T10:30:00.000000Z' spf_domain: type: string example: example.com seed_message: type: object properties: mail_from_name: type: string example: 'Test Sender' mail_from: type: string example: sender@example.com subject: type: string example: 'Seed message' preview_line: type: string example: 'This is a preview.' is_compliant: type: boolean example: true is_blocklisted: type: boolean example: false is_test: type: boolean example: false meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 2 per_page: type: integer example: 15 to: type: integer example: 15 total: type: integer example: 16 403: description: '' content: application/json: schema: type: object example: success: false message: 'Feed is only available for activated domains.' data: [] properties: success: type: boolean example: false message: type: string example: 'Feed is only available for activated domains.' data: type: array example: [] tags: - Domains parameters: - in: path name: id description: 'The ULID of the domain.' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV required: true schema: type: string '/api/v1/domains/{id}': get: summary: 'Retrieve Domain.' operationId: retrieveDomain description: '' parameters: - in: query name: start_date description: 'Filter related IPs from this date (Y-m-d). Maximum selected range is 90 days.' example: '2026-01-01' required: false schema: type: string description: 'Filter related IPs from this date (Y-m-d). Maximum selected range is 90 days.' example: '2026-01-01' - in: query name: end_date description: 'Filter related IPs until this date (Y-m-d). Maximum selected range is 90 days.' example: '2026-03-31' required: false schema: type: string description: 'Filter related IPs until this date (Y-m-d). Maximum selected range is 90 days.' example: '2026-03-31' - in: query name: related_ips_page description: 'The page number for related IP results.' example: 1 required: false schema: type: integer description: 'The page number for related IP results.' example: 1 responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Domain retrieved successfully.' data: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: example.com description: 'Primary sending domain' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' logo: null team: name: 'Owner Custom Team' mail_streams: - name: 'Transactional Stream' type: transactional logo: 'https://cdn.example.com/mail-streams/transactional.svg' configuration: spf_domains: [] dkim_selectors: [] bimi_selectors: [] dmarc: valid: true record: 'v=DMARC1; p=none;' mx: valid: true records: - mx1.example.com - mx2.example.com created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' alignment: items: [] settings: estimated_message_volume: '1000' general_settings: estimated_message_volume: '1000' shared_team_ids: [] mail_streams: - name: 'Transactional Stream' type: transactional general_settings: [] spf_domains: [] compliance: is_compliant: true pre_send: is_compliant: true mail_streams: - name: 'Transactional Stream' type: transactional is_compliant: true checks: ips: true one_click_unsub: false blocklists: true spf: true dkim: true dmarc: true tls: true post_send: is_compliant: false requirements: spf: true dkim: null dmarc_alignment: null dmarc_policy: false encryption: null user_reported_spam_rate: null dns_records: null one_click_unsubscribe: null honor_unsubscribe: null message_formatting: null subdomain: domain_id: mail.example.com requirements: spf: false dkim: false dmarc_alignment: null dmarc_policy: null encryption: null user_reported_spam_rate: null dns_records: null one_click_unsubscribe: null honor_unsubscribe: null message_formatting: null mail_streams: - name: 'Transactional Stream' type: transactional is_compliant: true checks: ips: true one_click_unsub: false blocklists: true spf: true dkim: true dmarc: true tls: true complaint_rate_averages: periods: [] related_ips: date_range: start_date: '2026-01-01 00:00:00' end_date: '2026-03-31 23:59:59' items: - id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: 192.0.2.10 description: 'Primary sending IP' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' meta: current_page: 1 last_page: 1 per_page: 10 total: 0 blocklists: is_blacklist: false blacklist_count: 0 listed_count: 0 listed_muted: 0 listed_details: [] properties: success: type: boolean example: true message: type: string example: 'Domain retrieved successfully.' data: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: type: string example: example.com description: type: string example: 'Primary sending domain' created_at: type: string example: '2026-03-28T10:30:00.000000Z' updated_at: type: string example: '2026-03-28T10:30:00.000000Z' logo: type: string example: null nullable: true team: type: object properties: name: type: string example: 'Owner Custom Team' mail_streams: type: array example: - name: 'Transactional Stream' type: transactional logo: 'https://cdn.example.com/mail-streams/transactional.svg' items: type: object properties: name: type: string example: 'Transactional Stream' type: type: string example: transactional logo: type: string example: 'https://cdn.example.com/mail-streams/transactional.svg' configuration: type: object properties: spf_domains: type: array example: [] dkim_selectors: type: array example: [] bimi_selectors: type: array example: [] dmarc: type: object properties: valid: type: boolean example: true record: type: string example: 'v=DMARC1; p=none;' mx: type: object properties: valid: type: boolean example: true records: type: array example: - mx1.example.com - mx2.example.com items: type: string created_at: type: string example: '2026-03-28T10:30:00.000000Z' updated_at: type: string example: '2026-03-28T10:30:00.000000Z' alignment: type: object properties: items: type: array example: [] settings: type: object properties: estimated_message_volume: type: string example: '1000' general_settings: type: object properties: estimated_message_volume: type: string example: '1000' shared_team_ids: type: array example: [] mail_streams: type: array example: - name: 'Transactional Stream' type: transactional general_settings: [] items: type: object properties: name: type: string example: 'Transactional Stream' type: type: string example: transactional general_settings: type: array example: [] spf_domains: type: array example: [] compliance: type: object properties: is_compliant: type: boolean example: true pre_send: type: object properties: is_compliant: type: boolean example: true mail_streams: type: array example: - name: 'Transactional Stream' type: transactional is_compliant: true checks: ips: true one_click_unsub: false blocklists: true spf: true dkim: true dmarc: true tls: true items: type: object properties: name: type: string example: 'Transactional Stream' type: type: string example: transactional is_compliant: type: boolean example: true checks: type: object properties: { ips: { type: boolean, example: true }, one_click_unsub: { type: boolean, example: false }, blocklists: { type: boolean, example: true }, spf: { type: boolean, example: true }, dkim: { type: boolean, example: true }, dmarc: { type: boolean, example: true }, tls: { type: boolean, example: true } } post_send: type: object properties: is_compliant: type: boolean example: false requirements: type: object properties: spf: type: boolean example: true dkim: type: string example: null nullable: true dmarc_alignment: type: string example: null nullable: true dmarc_policy: type: boolean example: false encryption: type: string example: null nullable: true user_reported_spam_rate: type: string example: null nullable: true dns_records: type: string example: null nullable: true one_click_unsubscribe: type: string example: null nullable: true honor_unsubscribe: type: string example: null nullable: true message_formatting: type: string example: null nullable: true subdomain: type: object properties: domain_id: type: string example: mail.example.com requirements: type: object properties: spf: { type: boolean, example: false } dkim: { type: boolean, example: false } dmarc_alignment: { type: string, example: null, nullable: true } dmarc_policy: { type: string, example: null, nullable: true } encryption: { type: string, example: null, nullable: true } user_reported_spam_rate: { type: string, example: null, nullable: true } dns_records: { type: string, example: null, nullable: true } one_click_unsubscribe: { type: string, example: null, nullable: true } honor_unsubscribe: { type: string, example: null, nullable: true } message_formatting: { type: string, example: null, nullable: true } mail_streams: type: array example: - name: 'Transactional Stream' type: transactional is_compliant: true checks: ips: true one_click_unsub: false blocklists: true spf: true dkim: true dmarc: true tls: true items: type: object properties: name: type: string example: 'Transactional Stream' type: type: string example: transactional is_compliant: type: boolean example: true checks: type: object properties: ips: type: boolean example: true one_click_unsub: type: boolean example: false blocklists: type: boolean example: true spf: type: boolean example: true dkim: type: boolean example: true dmarc: type: boolean example: true tls: type: boolean example: true complaint_rate_averages: type: object properties: periods: type: array example: [] related_ips: type: object properties: date_range: type: object properties: start_date: type: string example: '2026-01-01 00:00:00' end_date: type: string example: '2026-03-31 23:59:59' items: type: array example: - id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: 192.0.2.10 description: 'Primary sending IP' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' items: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: type: string example: 192.0.2.10 description: type: string example: 'Primary sending IP' created_at: type: string example: '2026-03-28T10:30:00.000000Z' updated_at: type: string example: '2026-03-28T10:30:00.000000Z' meta: type: object properties: current_page: type: integer example: 1 last_page: type: integer example: 1 per_page: type: integer example: 10 total: type: integer example: 0 blocklists: type: object properties: is_blacklist: type: boolean example: false blacklist_count: type: integer example: 0 listed_count: type: integer example: 0 listed_muted: type: integer example: 0 listed_details: type: array example: [] tags: - Domains requestBody: required: false content: application/json: schema: type: object properties: related_ips_page: type: integer description: 'Must be at least 1.' example: 16 nullable: true start_date: type: string description: 'Must be a valid date in the format Y-m-d. Must be a date before or equal to today. Must be a date before or equal to end_date.' example: '2022-08-31' nullable: true end_date: type: string description: 'Must be a valid date in the format Y-m-d. Must be a date before or equal to today. Must be a date after or equal to start_date.' example: '2052-08-29' nullable: true parameters: - in: path name: id description: 'The ULID of the domain.' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV required: true schema: type: string /api/v1/aprf-reports: get: summary: 'Retrieve APRF Reports.' operationId: retrieveAPRFReports description: "List the Comcast/Xfinity APRF (Aggregate Postmaster Report Feedback) reports\nreceived for this team in the given date range.\n\nThe collection is team-wide rather than nested under a domain on purpose:\na provider may send a rollup covering several domains or selectors, or a\nreport for a domain this team does not track, and both must stay reachable.\nThose reports carry `domain: null` and are never attributed to a tracked\ndomain by name.\n\nMetric values are provider-supplied approximate bucket upper bounds. They\nare not counts, so they must not be summed, averaged or turned into\npercentages; `metric_value_semantics` repeats that on every response." parameters: - in: query name: domain_id description: 'Optionally filter reports to a single accessible domain ULID. Rollups and reports for untracked domains carry no domain, so this filter excludes them.' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV required: false schema: type: string description: 'Optionally filter reports to a single accessible domain ULID. Rollups and reports for untracked domains carry no domain, so this filter excludes them.' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV - in: query name: start_date description: 'Filter reports whose report_date falls on or after this date (Y-m-d). Maximum selected range is 90 days. Defaults to the trailing 90 days.' example: '2026-05-09' required: false schema: type: string description: 'Filter reports whose report_date falls on or after this date (Y-m-d). Maximum selected range is 90 days. Defaults to the trailing 90 days.' example: '2026-05-09' - in: query name: end_date description: 'Filter reports whose report_date falls on or before this date (Y-m-d). Maximum selected range is 90 days.' example: '2026-08-06' required: false schema: type: string description: 'Filter reports whose report_date falls on or before this date (Y-m-d). Maximum selected range is 90 days.' example: '2026-08-06' - in: query name: sort description: 'Sort field. Either a report column (report_date, report_end, source, dkim_selector) or a single metric bucket as metric:.. Reports without the sorted metric are always ordered last.' example: report_date required: false schema: type: string description: 'Sort field. Either a report column (report_date, report_end, source, dkim_selector) or a single metric bucket as metric:.. Reports without the sorted metric are always ordered last.' example: report_date - in: query name: direction description: 'Sort direction, asc or desc.' example: desc required: false schema: type: string description: 'Sort direction, asc or desc.' example: desc - in: query name: page description: 'The page number for paginated report results.' example: 1 required: false schema: type: integer description: 'The page number for paginated report results.' example: 1 - in: query name: per_page description: 'Reports per page. Defaults to 25 and is clamped to a maximum of 100.' example: 25 required: false schema: type: integer description: 'Reports per page. Defaults to 25 and is clamped to a maximum of 100.' example: 25 responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'APRF reports retrieved successfully.' data: - id: 1421 domain: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: example.com is_rollup: false source: Comcast/Xfinity dkim_domain: example.com dkim_selector: s1 report_start: '2026-08-04T00:00:00+00:00' report_end: '2026-08-04T23:59:59+00:00' report_date: '2026-08-04' sender_verified: true billing: status: charged is_billed: true withheld_reason: null credits_used: 1 metrics: - category: classification metric_key: inbox approximate_bucket_upper_bound: 25 - id: 1420 domain: null is_rollup: true source: Comcast/Xfinity dkim_domain: '*.example.com' dkim_selector: '*' report_start: '2026-08-03T00:00:00+00:00' report_end: '2026-08-03T23:59:59+00:00' report_date: '2026-08-03' sender_verified: false billing: status: not_billable is_billed: false withheld_reason: null credits_used: 0 metrics: [] meta: current_page: 1 last_page: 1 per_page: 25 total: 2 date_range: start_date: '2026-05-09' end_date: '2026-08-06' sort: field: report_date direction: desc metric_value_semantics: provider_bucket_upper_bound_non_additive properties: success: type: boolean example: true message: type: string example: 'APRF reports retrieved successfully.' data: type: array example: - id: 1421 domain: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: example.com is_rollup: false source: Comcast/Xfinity dkim_domain: example.com dkim_selector: s1 report_start: '2026-08-04T00:00:00+00:00' report_end: '2026-08-04T23:59:59+00:00' report_date: '2026-08-04' sender_verified: true billing: status: charged is_billed: true withheld_reason: null credits_used: 1 metrics: - category: classification metric_key: inbox approximate_bucket_upper_bound: 25 - id: 1420 domain: null is_rollup: true source: Comcast/Xfinity dkim_domain: '*.example.com' dkim_selector: '*' report_start: '2026-08-03T00:00:00+00:00' report_end: '2026-08-03T23:59:59+00:00' report_date: '2026-08-03' sender_verified: false billing: status: not_billable is_billed: false withheld_reason: null credits_used: 0 metrics: [] items: type: object properties: id: type: integer example: 1421 domain: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: type: string example: example.com is_rollup: type: boolean example: false source: type: string example: Comcast/Xfinity dkim_domain: type: string example: example.com dkim_selector: type: string example: s1 report_start: type: string example: '2026-08-04T00:00:00+00:00' report_end: type: string example: '2026-08-04T23:59:59+00:00' report_date: type: string example: '2026-08-04' sender_verified: type: boolean example: true billing: type: object properties: status: type: string example: charged is_billed: type: boolean example: true withheld_reason: type: string example: null nullable: true credits_used: type: integer example: 1 metrics: type: array example: - category: classification metric_key: inbox approximate_bucket_upper_bound: 25 items: type: object properties: category: type: string example: classification metric_key: type: string example: inbox approximate_bucket_upper_bound: type: integer example: 25 meta: type: object properties: current_page: type: integer example: 1 last_page: type: integer example: 1 per_page: type: integer example: 25 total: type: integer example: 2 date_range: type: object properties: start_date: type: string example: '2026-05-09' end_date: type: string example: '2026-08-06' sort: type: object properties: field: type: string example: report_date direction: type: string example: desc metric_value_semantics: type: string example: provider_bucket_upper_bound_non_additive tags: - APRF '/api/v1/aprf-reports/{id}': get: summary: 'Retrieve APRF Report.' operationId: retrieveAPRFReport description: "Return one APRF report and its unsegmented metrics. Segmented metric rows\ndescribe slices of a report and would double-count, so they are never\nexposed.\n\nA report belonging to another team, a report we were never able to charge\nfor, and an identifier that does not exist all answer the same 404, so the\nresponse never reveals which case it was.\n\nThe id is typed as a string because `whereNumber` admits a value past bigint\nthat an int parameter would fail to coerce, answering 500 instead of the\nusual 404." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'APRF report retrieved successfully.' data: id: 1421 domain: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: example.com is_rollup: false source: Comcast/Xfinity dkim_domain: example.com dkim_selector: s1 report_start: '2026-08-04T00:00:00+00:00' report_end: '2026-08-04T23:59:59+00:00' report_date: '2026-08-04' sender_verified: true billing: status: charged is_billed: true withheld_reason: null credits_used: 1 metrics: - category: classification metric_key: inbox approximate_bucket_upper_bound: 25 metric_value_semantics: provider_bucket_upper_bound_non_additive properties: success: type: boolean example: true message: type: string example: 'APRF report retrieved successfully.' data: type: object properties: id: type: integer example: 1421 domain: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: type: string example: example.com is_rollup: type: boolean example: false source: type: string example: Comcast/Xfinity dkim_domain: type: string example: example.com dkim_selector: type: string example: s1 report_start: type: string example: '2026-08-04T00:00:00+00:00' report_end: type: string example: '2026-08-04T23:59:59+00:00' report_date: type: string example: '2026-08-04' sender_verified: type: boolean example: true billing: type: object properties: status: type: string example: charged is_billed: type: boolean example: true withheld_reason: type: string example: null nullable: true credits_used: type: integer example: 1 metrics: type: array example: - category: classification metric_key: inbox approximate_bucket_upper_bound: 25 items: type: object properties: category: type: string example: classification metric_key: type: string example: inbox approximate_bucket_upper_bound: type: integer example: 25 metric_value_semantics: type: string example: provider_bucket_upper_bound_non_additive 404: description: '' content: application/json: schema: type: object example: success: false message: 'APRF report not found.' data: [] properties: success: type: boolean example: false message: type: string example: 'APRF report not found.' data: type: array example: [] tags: - APRF parameters: - in: path name: id description: 'The APRF report identifier.' example: 1421 required: true schema: type: integer '/api/v1/domains/{id}/aprf-status': get: summary: 'Retrieve Domain APRF Status.' operationId: retrieveDomainAPRFStatus description: "Return the stored APRF (Aggregate Postmaster Report Feedback) DNS and\nconsent snapshot for one domain, plus the DNS records to publish so this\nteam receives reports for it.\n\nThis is a snapshot, never a live lookup: every record, verdict and message\nis what we last observed, and `checked_at` is the only indicator of how\nfresh that is. A stale `checked_at` means our snapshot is old, not that DNS\nchanged. There is no refresh parameter.\n\n`has_our_seed_address` is the one derived value. Selector records are shared\nbetween the teams that track the same domain, so the verdict cannot be\nstored against the record; it is computed per request from the requesting\nteam's own report address.\n\nThe response carries no metrics, so it carries no `metric_value_semantics`." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'APRF status retrieved successfully.' data: domain: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: example.com report_address: seed@a-abc123.seed.example.com wildcard: record: 'v=APRFv1; rua=mailto:seed@a-abc123.seed.example.com' valid: true message: 'APRF record is valid.' source: wildcard has_our_seed_address: true checked_at: '2026-08-05T09:00:00+00:00' selectors: - selector: s1 record: 'v=APRFv1; rua=mailto:seed@a-abc123.seed.example.com' valid: true message: 'APRF record is valid.' source: selector has_our_seed_address: true checked_at: '2026-08-05T09:00:00+00:00' sdi: header: MsgInfo separator: ^ recommended_records: - name: '*._aprf._domainkey.example.com' type: TXT value: 'v=APRFv1; rua=mailto:seed@a-abc123.seed.example.com; sdi=MsgInfo,^' scope: wildcard selector: null - name: s1._aprf._domainkey.example.com type: TXT value: 'v=APRFv1; rua=mailto:seed@a-abc123.seed.example.com; sdi=MsgInfo,^' scope: selector selector: s1 properties: success: type: boolean example: true message: type: string example: 'APRF status retrieved successfully.' data: type: object properties: domain: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: type: string example: example.com report_address: type: string example: seed@a-abc123.seed.example.com wildcard: type: object properties: record: type: string example: 'v=APRFv1; rua=mailto:seed@a-abc123.seed.example.com' valid: type: boolean example: true message: type: string example: 'APRF record is valid.' source: type: string example: wildcard has_our_seed_address: type: boolean example: true checked_at: type: string example: '2026-08-05T09:00:00+00:00' selectors: type: array example: - selector: s1 record: 'v=APRFv1; rua=mailto:seed@a-abc123.seed.example.com' valid: true message: 'APRF record is valid.' source: selector has_our_seed_address: true checked_at: '2026-08-05T09:00:00+00:00' items: type: object properties: selector: type: string example: s1 record: type: string example: 'v=APRFv1; rua=mailto:seed@a-abc123.seed.example.com' valid: type: boolean example: true message: type: string example: 'APRF record is valid.' source: type: string example: selector has_our_seed_address: type: boolean example: true checked_at: type: string example: '2026-08-05T09:00:00+00:00' sdi: type: object properties: header: type: string example: MsgInfo separator: type: string example: ^ recommended_records: type: array example: - name: '*._aprf._domainkey.example.com' type: TXT value: 'v=APRFv1; rua=mailto:seed@a-abc123.seed.example.com; sdi=MsgInfo,^' scope: wildcard selector: null - name: s1._aprf._domainkey.example.com type: TXT value: 'v=APRFv1; rua=mailto:seed@a-abc123.seed.example.com; sdi=MsgInfo,^' scope: selector selector: s1 items: type: object properties: name: type: string example: '*._aprf._domainkey.example.com' type: type: string example: TXT value: type: string example: 'v=APRFv1; rua=mailto:seed@a-abc123.seed.example.com; sdi=MsgInfo,^' scope: type: string example: wildcard selector: type: string example: null nullable: true tags: - APRF parameters: - in: path name: id description: 'The ULID of the domain.' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV required: true schema: type: string '/api/v1/domains/{id}/aprf-sdi': put: summary: 'Update Domain APRF SDI.' operationId: updateDomainAPRFSDI description: "Set or clear the optional Sender Defined Identifier pair for one domain.\nBoth fields must be sent: a header with a separator sets the pair, and two\nnulls clear it. One without the other is a 422, because a half-configured\npair cannot be written into a record.\n\nThis changes the records the domain owner must publish. The SDI pair is\nappended to every recommended record as `sdi=
,`, so a\nrecord published before this call no longer matches the recommendation\nafterwards. The response is the full status payload for exactly that\nreason: read `recommended_records` back and re-publish from it.\n\nNothing about the stored DNS snapshot changes here. No lookup is performed,\nevery `checked_at` is untouched, and the domain does not begin or stop\nreceiving reports because of this call.\n\nUnlike the read endpoint, this resolves the domain by owning team only. A\ndomain merely shared with this team is a 404 here: the SDI pair decides what\nits owner has to publish, so a team it was shared with must not rewrite it." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'APRF SDI updated successfully.' data: domain: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: example.com report_address: seed@a-abc123.seed.example.com wildcard: record: 'v=APRFv1; rua=mailto:seed@a-abc123.seed.example.com' valid: true message: 'APRF record is valid.' source: wildcard has_our_seed_address: true checked_at: '2026-08-05T09:00:00+00:00' selectors: [] sdi: header: MsgInfo separator: ^ recommended_records: - name: '*._aprf._domainkey.example.com' type: TXT value: 'v=APRFv1; rua=mailto:seed@a-abc123.seed.example.com; sdi=MsgInfo,^' scope: wildcard selector: null properties: success: type: boolean example: true message: type: string example: 'APRF SDI updated successfully.' data: type: object properties: domain: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: type: string example: example.com report_address: type: string example: seed@a-abc123.seed.example.com wildcard: type: object properties: record: type: string example: 'v=APRFv1; rua=mailto:seed@a-abc123.seed.example.com' valid: type: boolean example: true message: type: string example: 'APRF record is valid.' source: type: string example: wildcard has_our_seed_address: type: boolean example: true checked_at: type: string example: '2026-08-05T09:00:00+00:00' selectors: type: array example: [] sdi: type: object properties: header: type: string example: MsgInfo separator: type: string example: ^ recommended_records: type: array example: - name: '*._aprf._domainkey.example.com' type: TXT value: 'v=APRFv1; rua=mailto:seed@a-abc123.seed.example.com; sdi=MsgInfo,^' scope: wildcard selector: null items: type: object properties: name: type: string example: '*._aprf._domainkey.example.com' type: type: string example: TXT value: type: string example: 'v=APRFv1; rua=mailto:seed@a-abc123.seed.example.com; sdi=MsgInfo,^' scope: type: string example: wildcard selector: type: string example: null nullable: true tags: - APRF requestBody: required: false content: application/json: schema: type: object properties: aprf_sdi_header: type: string description: 'The header name providers should report the subscriber-data identifier under. Send null together with a null separator to clear the pair. Must not contain ; = , or :. The value field must be a string. Must match the regex /^[\x21-\x7E]+$/. Must not be greater than 255 characters.' example: MsgInfo nullable: true aprf_sdi_separator: type: string description: 'The single printable character separating the identifier fields. Must not be ; = or , because each would corrupt the published record. The value field must be a string. Must match the regex /^[\x20-\x7E]$/. Must not be one of ;, =, or , Must be 1 character.' example: ^ nullable: true parameters: - in: path name: id description: 'The ULID of the domain. Must be owned by this team; a domain only shared with it is not found here.' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV required: true schema: type: string /api/v1/ips: get: summary: 'Retrieve IPs.' operationId: retrieveIPs description: '' parameters: - in: query name: page description: 'The page number. Must be at least 1.' example: 1 required: false schema: type: integer description: 'The page number. Must be at least 1.' example: 1 - in: query name: per_page description: 'The number of IPs per page. Must be at least 1. Must not be greater than 100.' example: 15 required: false schema: type: integer description: 'The number of IPs per page. Must be at least 1. Must not be greater than 100.' example: 15 responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'IPs retrieved successfully.' data: - id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: 192.0.2.10 description: 'Primary sending IP' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' meta: current_page: 1 from: 1 last_page: 1 per_page: 15 to: 1 total: 1 properties: success: type: boolean example: true message: type: string example: 'IPs retrieved successfully.' data: type: array example: - id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: 192.0.2.10 description: 'Primary sending IP' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' items: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: type: string example: 192.0.2.10 description: type: string example: 'Primary sending IP' created_at: type: string example: '2026-03-28T10:30:00.000000Z' updated_at: type: string example: '2026-03-28T10:30:00.000000Z' meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 1 per_page: type: integer example: 15 to: type: integer example: 1 total: type: integer example: 1 tags: - IPs '/api/v1/ips/{id}/feed': get: summary: 'Retrieve IP Feed.' operationId: retrieveIPFeed description: '' parameters: - in: query name: page description: 'The page number for paginated feed results. Must be at least 1.' example: 1 required: false schema: type: integer description: 'The page number for paginated feed results. Must be at least 1.' example: 1 nullable: true - in: query name: date description: 'Filter feed items reported on or after this date, up to today. Must not be older than 90 days from today. Must be a valid date in the format Y-m-d.' example: '2026-08-06' required: false schema: type: string description: 'Filter feed items reported on or after this date, up to today. Must not be older than 90 days from today. Must be a valid date in the format Y-m-d.' example: '2026-08-06' nullable: true responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'IP feed retrieved successfully.' data: - reported_at: 'Mar 28, 2026 5:30PM' mail_stream: 'Transactional Stream' ip: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: 192.0.2.10 description: 'Primary sending IP' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' seed_message: mail_from_name: 'Test Sender' mail_from: sender@example.com subject: 'Seed message' preview_line: 'This is a preview.' is_compliant: true is_blocklisted: false is_test: false - message: 'IP is compliant.' reported_at: 'Mar 28, 2026 4:45PM' mail_stream: 'Transactional Stream' ip: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: 192.0.2.10 description: 'Primary sending IP' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' seed_message: null meta: current_page: 1 from: 1 last_page: 2 per_page: 15 to: 15 total: 16 properties: success: type: boolean example: true message: type: string example: 'IP feed retrieved successfully.' data: type: array example: - reported_at: 'Mar 28, 2026 5:30PM' mail_stream: 'Transactional Stream' ip: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: 192.0.2.10 description: 'Primary sending IP' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' seed_message: mail_from_name: 'Test Sender' mail_from: sender@example.com subject: 'Seed message' preview_line: 'This is a preview.' is_compliant: true is_blocklisted: false is_test: false - message: 'IP is compliant.' reported_at: 'Mar 28, 2026 4:45PM' mail_stream: 'Transactional Stream' ip: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: 192.0.2.10 description: 'Primary sending IP' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' seed_message: null items: type: object properties: reported_at: type: string example: 'Mar 28, 2026 5:30PM' mail_stream: type: string example: 'Transactional Stream' ip: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: type: string example: 192.0.2.10 description: type: string example: 'Primary sending IP' created_at: type: string example: '2026-03-28T10:30:00.000000Z' updated_at: type: string example: '2026-03-28T10:30:00.000000Z' seed_message: type: object properties: mail_from_name: type: string example: 'Test Sender' mail_from: type: string example: sender@example.com subject: type: string example: 'Seed message' preview_line: type: string example: 'This is a preview.' is_compliant: type: boolean example: true is_blocklisted: type: boolean example: false is_test: type: boolean example: false meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 2 per_page: type: integer example: 15 to: type: integer example: 15 total: type: integer example: 16 403: description: '' content: application/json: schema: type: object example: success: false message: 'Feed is only available for activated IPs.' data: [] properties: success: type: boolean example: false message: type: string example: 'Feed is only available for activated IPs.' data: type: array example: [] tags: - IPs parameters: - in: path name: id description: 'The ULID of the IP.' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV required: true schema: type: string '/api/v1/ips/{id}': get: summary: 'Retrieve IP.' operationId: retrieveIP description: '' parameters: - in: query name: start_date description: 'Filter related domains from this date (Y-m-d). Maximum selected range is 90 days.' example: '2026-01-01' required: false schema: type: string description: 'Filter related domains from this date (Y-m-d). Maximum selected range is 90 days.' example: '2026-01-01' - in: query name: end_date description: 'Filter related domains until this date (Y-m-d). Maximum selected range is 90 days.' example: '2026-03-31' required: false schema: type: string description: 'Filter related domains until this date (Y-m-d). Maximum selected range is 90 days.' example: '2026-03-31' - in: query name: related_domains_page description: 'The page number for related domain results.' example: 1 required: false schema: type: integer description: 'The page number for related domain results.' example: 1 responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'IP retrieved successfully.' data: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: 192.0.2.10 description: 'Primary sending IP' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' logo: null team: name: 'Owner Custom Team' mail_streams: - name: 'Transactional Stream' type: transactional logo: 'https://cdn.example.com/mail-streams/transactional.svg' configuration: rdns: mail.example.com fcrdns: true rdns_summary: null provider: asn: asn: AS64500 name: 'Example ASN' location: '37.7749,-122.4194' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' settings: shared_team_ids: [] compliance: is_compliant: true sender_score: 98 checks: blocklists: true fcrdns: true complaint_rate_averages: periods: [] inbox_placement_averages: periods: [] related_domains: date_range: start_date: '2026-01-01 00:00:00' end_date: '2026-03-31 23:59:59' items: - id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: example.com description: 'Primary sending domain' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' meta: current_page: 1 last_page: 1 per_page: 10 total: 0 blocklists: is_blacklist: false blacklist_count: 0 listed_count: 0 listed_muted: 0 listed_details: [] properties: success: type: boolean example: true message: type: string example: 'IP retrieved successfully.' data: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: type: string example: 192.0.2.10 description: type: string example: 'Primary sending IP' created_at: type: string example: '2026-03-28T10:30:00.000000Z' updated_at: type: string example: '2026-03-28T10:30:00.000000Z' logo: type: string example: null nullable: true team: type: object properties: name: type: string example: 'Owner Custom Team' mail_streams: type: array example: - name: 'Transactional Stream' type: transactional logo: 'https://cdn.example.com/mail-streams/transactional.svg' items: type: object properties: name: type: string example: 'Transactional Stream' type: type: string example: transactional logo: type: string example: 'https://cdn.example.com/mail-streams/transactional.svg' configuration: type: object properties: rdns: type: string example: mail.example.com fcrdns: type: boolean example: true rdns_summary: type: string example: null nullable: true provider: type: object properties: asn: type: object properties: asn: type: string example: AS64500 name: type: string example: 'Example ASN' location: type: string example: '37.7749,-122.4194' created_at: type: string example: '2026-03-28T10:30:00.000000Z' updated_at: type: string example: '2026-03-28T10:30:00.000000Z' settings: type: object properties: shared_team_ids: type: array example: [] compliance: type: object properties: is_compliant: type: boolean example: true sender_score: type: integer example: 98 checks: type: object properties: blocklists: type: boolean example: true fcrdns: type: boolean example: true complaint_rate_averages: type: object properties: periods: type: array example: [] inbox_placement_averages: type: object properties: periods: type: array example: [] related_domains: type: object properties: date_range: type: object properties: start_date: type: string example: '2026-01-01 00:00:00' end_date: type: string example: '2026-03-31 23:59:59' items: type: array example: - id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: example.com description: 'Primary sending domain' created_at: '2026-03-28T10:30:00.000000Z' updated_at: '2026-03-28T10:30:00.000000Z' items: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: type: string example: example.com description: type: string example: 'Primary sending domain' created_at: type: string example: '2026-03-28T10:30:00.000000Z' updated_at: type: string example: '2026-03-28T10:30:00.000000Z' meta: type: object properties: current_page: type: integer example: 1 last_page: type: integer example: 1 per_page: type: integer example: 10 total: type: integer example: 0 blocklists: type: object properties: is_blacklist: type: boolean example: false blacklist_count: type: integer example: 0 listed_count: type: integer example: 0 listed_muted: type: integer example: 0 listed_details: type: array example: [] tags: - IPs requestBody: required: false content: application/json: schema: type: object properties: related_domains_page: type: integer description: 'Must be at least 1.' example: 16 nullable: true start_date: type: string description: 'Must be a valid date in the format Y-m-d. Must be a date before or equal to today. Must be a date before or equal to end_date.' example: '2022-08-31' nullable: true end_date: type: string description: 'Must be a valid date in the format Y-m-d. Must be a date before or equal to today. Must be a date after or equal to start_date.' example: '2052-08-29' nullable: true parameters: - in: path name: id description: 'The ULID of the IP.' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV required: true schema: type: string '/api/v1/intelligence/single-emails/{email}': get: summary: 'Retrieve Single Email.' operationId: retrieveSingleEmail description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: id: 11 email: price.amber@example.org status: null created_at: '2026-08-06 14:24:38' updated_at: '2026-08-06 14:24:38' properties: data: type: object properties: id: type: integer example: 11 email: type: string example: price.amber@example.org status: type: string example: null nullable: true created_at: type: string example: '2026-08-06 14:24:38' updated_at: type: string example: '2026-08-06 14:24:38' tags: - Intelligence delete: summary: 'Delete Single Email.' operationId: deleteSingleEmail description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: message: 'Single email deleted.' deleted: true properties: message: type: string example: 'Single email deleted.' deleted: type: boolean example: true tags: - Intelligence parameters: - in: path name: email description: 'The email address to retrieve.' example: example@postmasterplus.com required: true schema: type: string /api/v1/intelligence/single-emails/scan: post: summary: 'Scan Single Email.' operationId: scanSingleEmail description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: id: 12 email: rempel.chadrick@example.org status: null created_at: '2026-08-06 14:24:38' updated_at: '2026-08-06 14:24:38' properties: data: type: object properties: id: type: integer example: 12 email: type: string example: rempel.chadrick@example.org status: type: string example: null nullable: true created_at: type: string example: '2026-08-06 14:24:38' updated_at: type: string example: '2026-08-06 14:24:38' tags: - Intelligence requestBody: required: true content: application/json: schema: type: object properties: email: type: string description: 'The email address to scan.' example: example@postmasterplus.com actions: type: string description: 'The actions in comma separated format to perform.' example: '"verification,activity,postal_append"' required: - email - actions /api/v1/blocklist/scan/start: post: summary: 'Start Blocklist Scan.' operationId: startBlocklistScan description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: id: 01kzbqcfbr82g17w8vcqgfqn8h status: pending status_label: Pending unique_hosts_checked: 3 total_hosts_detected: null urls_scanned: 0 urls_skipped: 0 blocklisted: false credits_used: 0 error_message: '' started_at: null completed_at: null success: true message: 'Blocklist check started successfully.' properties: data: type: object properties: id: type: string example: 01kzbqcfbr82g17w8vcqgfqn8h description: 'The unique identifier for the blocklist check.' status: type: string example: pending description: 'The status of the blocklist check. Example: `pending`, `processing`, `completed`, `partially_completed`, `failed`' enum: - pending - processing - completed - partially_completed - failed status_label: type: string example: Pending description: 'Human-friendly status label. Example: Pending, Processing, Completed, Partially Completed, Failed' unique_hosts_checked: type: integer example: 3 description: 'The number of unique hosts checked.' total_hosts_detected: type: string example: null description: 'The number of hosts detected.' urls_scanned: type: integer example: 0 description: 'The number of URLs scanned.' urls_skipped: type: integer example: 0 description: 'The number of URLs skipped.' blocklisted: type: boolean example: false description: 'Whether the check is blocklisted.' credits_used: type: integer example: 0 description: 'The number of credits used.' error_message: type: string example: '' description: 'The error message if the check failed.' started_at: type: string example: null description: 'The date and time the check started.' completed_at: type: string example: null description: 'The date and time the check completed.' required: - id - status - status_label - unique_hosts_checked - total_hosts_detected - urls_scanned - urls_skipped - blocklisted - credits_used - error_message - started_at - completed_at success: type: boolean example: true message: type: string example: 'Blocklist check started successfully.' tags: - Blocklists requestBody: required: true content: application/json: schema: type: object properties: urls: type: array description: 'Array of HTTP(S) URLs, bare domains, or public IP addresses to scan (minimum 1 target, maximum 100 targets).' example: - 'https://optipub.com/newsletter' - optipub.com - 8.8.8.8 items: type: string follow_redirects: type: boolean description: 'optional Whether to follow redirects for full HTTP(S) URL targets. Defaults to true. Bare domains and IP targets always bypass redirect processing.' example: true required: - urls '/api/v1/blocklist/scan/status/{id}': get: summary: 'Retrieve Blocklist Scan Status.' operationId: retrieveBlocklistScanStatus description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: id: 01kzbqcfc62awwsmrkkc7qn50r status: pending status_label: Pending unique_hosts_checked: 4 total_hosts_detected: null urls_scanned: 0 urls_skipped: 0 blocklisted: false credits_used: 0 error_message: '' started_at: null completed_at: null success: true message: 'Blocklist check status retrieved successfully.' properties: data: type: object properties: id: type: string example: 01kzbqcfc62awwsmrkkc7qn50r description: 'The unique identifier for the blocklist check.' status: type: string example: pending description: 'The status of the blocklist check. Example: `pending`, `processing`, `completed`, `partially_completed`, `failed`' enum: - pending - processing - completed - partially_completed - failed status_label: type: string example: Pending description: 'Human-friendly status label. Example: Pending, Processing, Completed, Partially Completed, Failed' unique_hosts_checked: type: integer example: 4 description: 'The number of unique hosts checked.' total_hosts_detected: type: string example: null description: 'The number of hosts detected.' urls_scanned: type: integer example: 0 description: 'The number of URLs scanned.' urls_skipped: type: integer example: 0 description: 'The number of URLs skipped.' blocklisted: type: boolean example: false description: 'Whether the check is blocklisted.' credits_used: type: integer example: 0 description: 'The number of credits used.' error_message: type: string example: '' description: 'The error message if the check failed.' started_at: type: string example: null description: 'The date and time the check started.' completed_at: type: string example: null description: 'The date and time the check completed.' required: - id - status - status_label - unique_hosts_checked - total_hosts_detected - urls_scanned - urls_skipped - blocklisted - credits_used - error_message - started_at - completed_at success: type: boolean example: true message: type: string example: 'Blocklist check status retrieved successfully.' tags: - Blocklists parameters: - in: path name: id description: 'The ULID of the blocklist check.' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV required: true schema: type: string /api/v1/seedboxes: get: summary: '' operationId: getApiV1Seedboxes description: '' parameters: [] responses: {} tags: - Seedbox requestBody: required: false content: application/json: schema: type: object properties: status: type: string description: '' example: active enum: - active - revoked nullable: true mode: type: string description: '' example: null client_reference: type: string description: 'Must not be greater than 255 characters.' example: b nullable: true per_page: type: integer description: 'Must be at least 1. Must not be greater than 100.' example: 22 page: type: integer description: 'Must be at least 1.' example: 67 post: summary: '' operationId: postApiV1Seedboxes description: '' parameters: [] responses: {} tags: - Seedbox requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Must not be greater than 120 characters.' example: b client_reference: type: string description: 'Must not be greater than 255 characters.' example: 'n' nullable: true scan_blocklists: type: boolean description: '' example: true mode: type: string description: '' example: null max_messages: type: string description: '' example: null expires_at: type: string description: '' example: null required: - name '/api/v1/seedboxes/{seedbox}/messages': get: summary: '' operationId: getApiV1SeedboxesSeedboxMessages description: '' parameters: [] responses: {} tags: - Seedbox requestBody: required: false content: application/json: schema: type: object properties: per_page: type: integer description: 'Must be at least 1. Must not be greater than 100.' example: 1 page: type: integer description: 'Must be at least 1.' example: 22 include_content: type: string description: '' example: 'true' enum: - 'true' - 'false' - '1' - '0' parameters: - in: path name: seedbox description: 'The seedbox.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/api/v1/seedboxes/{seedbox}/messages/{message}': get: summary: '' operationId: getApiV1SeedboxesSeedboxMessagesMessage description: '' parameters: [] responses: {} tags: - Seedbox requestBody: required: false content: application/json: schema: type: object properties: include_content: type: string description: '' example: '1' enum: - 'true' - 'false' - '1' - '0' parameters: - in: path name: seedbox description: 'The seedbox.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string - in: path name: message description: 'The message.' example: BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc required: true schema: type: string '/api/v1/seedboxes/{seedbox}/messages/{message}/unsubscribe': post: summary: 'Request a one-click unsubscribe for the list behind a seedbox message.' operationId: requestAOneClickUnsubscribeForTheListBehindASeedboxMessage description: "Mirrors the web endpoint exactly: this action owns no unsubscribe logic\nof its own, it resolves the message through the current team, delegates\nto the shared action, and maps the returned outcome to a status through\n{@see RequestUnsubscribeOutcome}, so every surface agrees on whether an\noutcome counts as a success. A `rate_limited` outcome answers 429 and the same\nrequest may succeed once the budget refills." parameters: [] responses: {} tags: - Seedbox parameters: - in: path name: seedbox description: 'The seedbox.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string - in: path name: message description: 'The message.' example: BcECdBDA-CdED-bFEA-CbCE-BcCdeBfbbebc required: true schema: type: string '/api/v1/seedboxes/{seedbox_id}/subscriptions': get: summary: '' operationId: getApiV1SeedboxesSeedbox_idSubscriptions description: '' parameters: [] responses: {} tags: - Seedbox requestBody: required: false content: application/json: schema: type: object properties: state: type: string description: '' example: subscribed enum: - subscribed - unsubscribe_pending - unsubscribe_failed - unsubscribed - mail_after_unsubscribe_within_threshold - mail_after_unsubscribe nullable: true search: type: string description: 'Must not be greater than 255 characters.' example: b nullable: true sort: type: string description: '' example: last_seen_at enum: - severity - last_seen_at - oldest_seen_at nullable: true per_page: type: integer description: 'Must be at least 1. Must not be greater than 100.' example: 22 page: type: integer description: 'Must be at least 1.' example: 67 parameters: - in: path name: seedbox_id description: 'The ID of the seedbox.' example: architecto required: true schema: type: string '/api/v1/seedboxes/{seedbox}/subscriptions/unsubscribe-all': post: summary: 'Request an unsubscribe from every actionable list matching the given filters.' operationId: requestAnUnsubscribeFromEveryActionableListMatchingTheGivenFilters description: "Requests are paced, so this answers 202 with counts rather than a per-list\noutcome." parameters: [] responses: {} tags: - Seedbox requestBody: required: false content: application/json: schema: type: object properties: state: type: string description: '' example: mail_after_unsubscribe enum: - subscribed - unsubscribe_pending - unsubscribe_failed - unsubscribed - mail_after_unsubscribe_within_threshold - mail_after_unsubscribe nullable: true search: type: string description: 'Must not be greater than 255 characters.' example: b nullable: true sort: type: string description: '' example: severity enum: - severity - last_seen_at - oldest_seen_at nullable: true parameters: - in: path name: seedbox description: 'The seedbox.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/api/v1/seedboxes/{seedbox}/subscriptions/{subscription}/unsubscribe': post: summary: '' operationId: postApiV1SeedboxesSeedboxSubscriptionsSubscriptionUnsubscribe description: '' parameters: [] responses: {} tags: - Seedbox parameters: - in: path name: seedbox description: 'The seedbox.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string - in: path name: subscription description: 'The subscription.' example: '564' required: true schema: type: string '/api/v1/seedboxes/{seedbox}': get: summary: '' operationId: getApiV1SeedboxesSeedbox description: '' parameters: [] responses: {} tags: - Seedbox patch: summary: 'Update editable seedbox metadata without changing its address or lifecycle.' operationId: updateEditableSeedboxMetadataWithoutChangingItsAddressOrLifecycle description: '' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: id: 01kzbqcfemk8k9bj46s9gx4jvc team_id: 44 name: null client_reference: null status: active address: opti@b-01kzbqcfemk8k9bj46s9gx4jvd.seeds.dev.optipub.com scan_blocklists: false receive_credits_per_message: 1 messages_count: 0 rejected_insufficient_credit_count: 0 unresolved_insufficient_credit_rejection_count: 0 accepting_messages: true can_delete: false revoked_at: null last_received_at: null last_rejected_insufficient_credit_at: null created_at: '2026-08-06T14:24:38+00:00' success: true message: 'Seedbox updated successfully.' properties: data: type: object properties: id: type: string example: 01kzbqcfemk8k9bj46s9gx4jvc team_id: type: integer example: 44 name: type: string example: null nullable: true client_reference: type: string example: null nullable: true status: type: string example: active address: type: string example: opti@b-01kzbqcfemk8k9bj46s9gx4jvd.seeds.dev.optipub.com scan_blocklists: type: boolean example: false receive_credits_per_message: type: integer example: 1 messages_count: type: integer example: 0 rejected_insufficient_credit_count: type: integer example: 0 unresolved_insufficient_credit_rejection_count: type: integer example: 0 accepting_messages: type: boolean example: true can_delete: type: boolean example: false revoked_at: type: string example: null nullable: true last_received_at: type: string example: null nullable: true last_rejected_insufficient_credit_at: type: string example: null nullable: true created_at: type: string example: '2026-08-06T14:24:38+00:00' success: type: boolean example: true message: type: string example: 'Seedbox updated successfully.' tags: - Seedbox requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: 'A non-empty human-readable name. Omit this field to preserve the current name. Must not be greater than 120 characters.' example: 'Transactional seedbox' client_reference: type: string description: 'A stable external identifier. Send null to clear it, or omit this field to preserve it. Must not be greater than 255 characters.' example: campaign-2026-07 nullable: true scan_blocklists: type: boolean description: 'Whether future received messages should be scanned against blocklists. Enabling it requires blocklist-check:scan:start and may increase future credit usage.' example: true delete: summary: '' operationId: deleteApiV1SeedboxesSeedbox description: '' parameters: [] responses: {} tags: - Seedbox parameters: - in: path name: seedbox description: 'The seedbox.' example: 58C6f7nnFCJ9M6rjyc6rTKMzn6 required: true schema: type: string '/api/v1/seedboxes/{seedbox}/delete': delete: summary: '' operationId: deleteApiV1SeedboxesSeedboxDelete description: '' parameters: [] responses: {} tags: - Seedbox parameters: - in: path name: seedbox description: 'The seedbox.' example: 58 required: true schema: type: integer /api/v1/spam-identifiers: get: summary: 'Retrieve Spam Identifiers.' operationId: retrieveSpamIdentifiers description: "List all Gmail Postmaster Tools v2 spam (feedback loop) identifiers reported\nfor accessible domains in the given date range, along with each identifier's\naverage complaint rate across that window." parameters: - in: query name: domain_id description: 'Optionally filter identifiers to a single accessible domain ULID.' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV required: false schema: type: string description: 'Optionally filter identifiers to a single accessible domain ULID.' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV - in: query name: page description: 'The page number for paginated identifier results.' example: 1 required: false schema: type: integer description: 'The page number for paginated identifier results.' example: 1 - in: query name: start_date description: 'Filter identifiers from this date (Y-m-d). Maximum selected range is 90 days.' example: '2026-01-22' required: false schema: type: string description: 'Filter identifiers from this date (Y-m-d). Maximum selected range is 90 days.' example: '2026-01-22' - in: query name: end_date description: 'Filter identifiers until this date (Y-m-d). Maximum selected range is 90 days.' example: '2026-04-22' required: false schema: type: string description: 'Filter identifiers until this date (Y-m-d). Maximum selected range is 90 days.' example: '2026-04-22' responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Spam identifiers retrieved successfully.' data: - domain: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: example.com identifier: '66189376' sample_count: 57 average_complaint_rate: 0.12 min_complaint_rate: 0.05 max_complaint_rate: 0.31 - domain: id: 01ARZ3NDEKTSV4RRFFQ69G5FBG value: example.net identifier: '66189799' sample_count: 40 average_complaint_rate: 0.05 min_complaint_rate: 0.01 max_complaint_rate: 0.14 meta: current_page: 1 last_page: 1 per_page: 25 total: 2 properties: success: type: boolean example: true message: type: string example: 'Spam identifiers retrieved successfully.' data: type: array example: - domain: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: example.com identifier: '66189376' sample_count: 57 average_complaint_rate: 0.12 min_complaint_rate: 0.05 max_complaint_rate: 0.31 - domain: id: 01ARZ3NDEKTSV4RRFFQ69G5FBG value: example.net identifier: '66189799' sample_count: 40 average_complaint_rate: 0.05 min_complaint_rate: 0.01 max_complaint_rate: 0.14 items: type: object properties: domain: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: type: string example: example.com identifier: type: string example: '66189376' sample_count: type: integer example: 57 average_complaint_rate: type: number example: 0.12 min_complaint_rate: type: number example: 0.05 max_complaint_rate: type: number example: 0.31 meta: type: object properties: current_page: type: integer example: 1 last_page: type: integer example: 1 per_page: type: integer example: 25 total: type: integer example: 2 tags: - 'Spam Identifiers' '/api/v1/spam-identifiers/{identifier}': get: summary: 'Retrieve Spam Identifier.' operationId: retrieveSpamIdentifier description: "Return the daily Gmail complaint rate time series for a single spam\n(feedback loop) identifier across accessible domains, plus the aggregate\naverage over the selected date range for each domain." parameters: - in: query name: domain_id description: 'Optionally filter the identifier to a single accessible domain ULID.' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV required: false schema: type: string description: 'Optionally filter the identifier to a single accessible domain ULID.' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV - in: query name: start_date description: 'Start date of the reporting window (Y-m-d). Maximum selected range is 90 days.' example: '2026-01-22' required: false schema: type: string description: 'Start date of the reporting window (Y-m-d). Maximum selected range is 90 days.' example: '2026-01-22' - in: query name: end_date description: 'End date of the reporting window (Y-m-d). Maximum selected range is 90 days.' example: '2026-04-22' required: false schema: type: string description: 'End date of the reporting window (Y-m-d). Maximum selected range is 90 days.' example: '2026-04-22' responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Spam identifier retrieved successfully.' data: - identifier: '66189376' domain: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: example.com date_range: start_date: '2026-01-22' end_date: '2026-04-22' aggregate: average_complaint_rate: 0.12 sample_count: 57 time_series: - date: '2026-01-22' complaint_rate: 0.1 - date: '2026-01-23' complaint_rate: null properties: success: type: boolean example: true message: type: string example: 'Spam identifier retrieved successfully.' data: type: array example: - identifier: '66189376' domain: id: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: example.com date_range: start_date: '2026-01-22' end_date: '2026-04-22' aggregate: average_complaint_rate: 0.12 sample_count: 57 time_series: - date: '2026-01-22' complaint_rate: 0.1 - date: '2026-01-23' complaint_rate: null items: type: object properties: identifier: type: string example: '66189376' domain: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV value: type: string example: example.com date_range: type: object properties: start_date: type: string example: '2026-01-22' end_date: type: string example: '2026-04-22' aggregate: type: object properties: average_complaint_rate: type: number example: 0.12 sample_count: type: integer example: 57 time_series: type: array example: - date: '2026-01-22' complaint_rate: 0.1 - date: '2026-01-23' complaint_rate: null items: type: object properties: date: type: string example: '2026-01-22' complaint_rate: type: number example: 0.1 404: description: '' content: application/json: schema: type: object example: success: false message: 'Spam identifier not found for accessible domains in the selected date range.' data: [] properties: success: type: boolean example: false message: type: string example: 'Spam identifier not found for accessible domains in the selected date range.' data: type: array example: [] tags: - 'Spam Identifiers' parameters: - in: path name: identifier description: 'The Gmail Postmaster Tools v2 spam (feedback loop) identifier.' example: '66189376' required: true schema: type: string /api/v1/screenshot/take: post: summary: 'Take Screenshot.' operationId: takeScreenshot description: 'Take a screenshot of a URL or HTML content.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: id: 01kzbqcfcvv6024pw79n8d297q url: 'https://assets.optipub.com/postmaster/screenshots/01kzbqcfcnw7xyx8d4st3qqdet/01kzbqcfcvv6024pw79n8d297q.png' format: png width: 1024 height: 600 device_scale: 3 credits_used: 1 started_at: '2026-07-27T08:26:12+00:00' completed_at: '2026-07-27T08:26:25+00:00' created_at: '2026-08-06T14:24:38+00:00' success: true message: 'Screenshot taken successfully.' properties: data: type: object properties: id: type: string example: 01kzbqcfcvv6024pw79n8d297q description: 'The unique identifier for the screenshot.' url: type: string example: 'https://assets.optipub.com/postmaster/screenshots/01kzbqcfcnw7xyx8d4st3qqdet/01kzbqcfcvv6024pw79n8d297q.png' description: 'The URL of the screenshot image.' format: type: string example: png description: 'The format of the screenshot image. Example: `png`, `jpeg`' width: type: integer example: 1024 description: 'The width of the screenshot in pixels.' height: type: integer example: 600 description: 'The height of the screenshot in pixels.' device_scale: type: integer example: 3 description: 'The device scale factor used (1-3). Higher values produce sharper images.' credits_used: type: integer example: 1 description: 'The number of credits used for this screenshot.' started_at: type: string example: '2026-07-27T08:26:12+00:00' description: 'The date and time when the screenshot capture started.' completed_at: type: string example: '2026-07-27T08:26:25+00:00' description: 'The date and time when the screenshot capture completed.' created_at: type: string example: '2026-08-06T14:24:38+00:00' description: 'The date and time the screenshot was created.' required: - id - url - format - width - height - device_scale - credits_used - started_at - completed_at - created_at success: type: boolean example: true message: type: string example: 'Screenshot taken successfully.' tags: - Tools requestBody: required: false content: application/json: schema: type: object properties: url: type: string description: 'URL to screenshot (required if html is not provided).' example: '"https://example.com"' nullable: true html: type: string description: 'HTML content to screenshot (required if url is not provided).' example: null nullable: true width: type: integer description: 'optional Viewport width in pixels (320-1920). Defaults to 1280.' example: 1280 nullable: true height: type: integer description: 'optional Viewport height in pixels (240-1080). Defaults to 720.' example: 720 nullable: true format: type: string description: 'optional Image format (png, jpeg, or webp). Defaults to png.' example: '"png"' nullable: true device_scale: type: integer description: 'optional Device scale factor for higher resolution (1-3). Defaults to 1 for retina-quality images.' example: 1 nullable: true /api/v1/screenshots: get: summary: 'List Screenshots.' operationId: listScreenshots description: 'Retrieve a paginated list of screenshots for the current team.' parameters: - in: query name: per_page description: 'The number of screenshots per page (1-100). Defaults to 15. Must be at least 1. Must not be greater than 100.' example: 15 required: false schema: type: integer description: 'The number of screenshots per page (1-100). Defaults to 15. Must be at least 1. Must not be greater than 100.' example: 15 - in: query name: page description: 'The page number. Must be at least 1.' example: 1 required: false schema: type: integer description: 'The page number. Must be at least 1.' example: 1 - in: query name: sort description: 'Sort order. Use `-` prefix for descending. Options: `created_at`, `-created_at`, `format`, `-format`. Defaults to `-created_at`.' example: '-created_at' required: false schema: type: string description: 'Sort order. Use `-` prefix for descending. Options: `created_at`, `-created_at`, `format`, `-format`. Defaults to `-created_at`.' example: '-created_at' enum: - created_at - '-created_at' - format - '-format' responses: 200: description: '' content: application/json: schema: type: object example: success: true message: 'Screenshots retrieved successfully.' data: - id: 01ARZ3NDEKTSV4RRFFQ69G5FAV url: 'https://assets.optipub.com/postmaster/screenshots/01arz3ndektsv4rrffq69g5fav/01arz3ndektsv4rrffq69g5fav.png' format: png width: 1280 height: 720 credits_used: 1 created_at: '2024-01-15T10:30:00Z' meta: current_page: 1 from: 1 last_page: 5 per_page: 15 to: 15 total: 75 properties: success: type: boolean example: true message: type: string example: 'Screenshots retrieved successfully.' data: type: array example: - id: 01ARZ3NDEKTSV4RRFFQ69G5FAV url: 'https://assets.optipub.com/postmaster/screenshots/01arz3ndektsv4rrffq69g5fav/01arz3ndektsv4rrffq69g5fav.png' format: png width: 1280 height: 720 credits_used: 1 created_at: '2024-01-15T10:30:00Z' items: type: object properties: id: type: string example: 01ARZ3NDEKTSV4RRFFQ69G5FAV url: type: string example: 'https://assets.optipub.com/postmaster/screenshots/01arz3ndektsv4rrffq69g5fav/01arz3ndektsv4rrffq69g5fav.png' format: type: string example: png width: type: integer example: 1280 height: type: integer example: 720 credits_used: type: integer example: 1 created_at: type: string example: '2024-01-15T10:30:00Z' meta: type: object properties: current_page: type: integer example: 1 from: type: integer example: 1 last_page: type: integer example: 5 per_page: type: integer example: 15 to: type: integer example: 15 total: type: integer example: 75 tags: - Tools '/api/v1/screenshot/{id}': get: summary: 'Get Screenshot.' operationId: getScreenshot description: 'Retrieve details of a specific screenshot by its ID.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: id: 01kzbqcfdg45vbz3srh76hngnv url: 'https://assets.optipub.com/postmaster/screenshots/01kzbqcfdb2gan5pw88jsajkfx/01kzbqcfdg45vbz3srh76hngnv.png' format: png width: 800 height: 600 device_scale: 2 credits_used: 1 started_at: '2026-07-27T05:07:02+00:00' completed_at: '2026-07-27T05:07:32+00:00' created_at: '2026-08-06T14:24:38+00:00' success: true message: 'Screenshot retrieved successfully.' properties: data: type: object properties: id: type: string example: 01kzbqcfdg45vbz3srh76hngnv description: 'The unique identifier for the screenshot.' url: type: string example: 'https://assets.optipub.com/postmaster/screenshots/01kzbqcfdb2gan5pw88jsajkfx/01kzbqcfdg45vbz3srh76hngnv.png' description: 'The URL of the screenshot image.' format: type: string example: png description: 'The format of the screenshot image. Example: `png`, `jpeg`' width: type: integer example: 800 description: 'The width of the screenshot in pixels.' height: type: integer example: 600 description: 'The height of the screenshot in pixels.' device_scale: type: integer example: 2 description: 'The device scale factor used (1-3). Higher values produce sharper images.' credits_used: type: integer example: 1 description: 'The number of credits used for this screenshot.' started_at: type: string example: '2026-07-27T05:07:02+00:00' description: 'The date and time when the screenshot capture started.' completed_at: type: string example: '2026-07-27T05:07:32+00:00' description: 'The date and time when the screenshot capture completed.' created_at: type: string example: '2026-08-06T14:24:38+00:00' description: 'The date and time the screenshot was created.' required: - id - url - format - width - height - device_scale - credits_used - started_at - completed_at - created_at success: type: boolean example: true message: type: string example: 'Screenshot retrieved successfully.' tags: - Tools parameters: - in: path name: id description: 'The ULID of the screenshot.' example: 01ARZ3NDEKTSV4RRFFQ69G5FAV required: true schema: type: string