# Check whether a sending domain's infrastructure is blocklisted

`GET /v1/email/inbox-insights/blocklists`

Checks every sending IP behind a sending domain against the blocklists
receivers consult, and returns what is listed now plus the listings seen
recently against each target.

The check runs when the request is made, so this is a live lookup rather
than a measurement over a period: there is no window, and only the
freshness lag hint applies. Providers that publish several lists are
reported per list, because what a listing means and how it is cleared
differ between them.

Each target is looked up separately, so one can fail while the rest
succeed. A target nobody managed to check comes back with its `status`
reporting that and its `checked_at` null, rather than as a target that
came back clear, and `active_count` is null rather than zero when no target
could be checked at all. That is what keeps "nothing is listed" and "the
check did not run" from being mistaken for each other. A `503` means Bird
could not reach the lookup service at all, which is a different answer from
a lookup that ran and reported nothing.

API-key calls require Insights preview access for your organization.

## Code samples

### TypeScript

```ts
// Requires Insights preview access for the organization.
let sendingDomain: string | undefined;
for await (const domain of bird.email.inboxInsights.domains.list({ search: "mail.example.com" })) {
  if (domain.domain === "mail.example.com") { sendingDomain = domain.domain; break; }
}
if (!sendingDomain) throw new Error("Verify mail.example.com in this workspace first");
const report = await bird.email.inboxInsights.blocklists({ sending_domain: sendingDomain });
console.log(report);
```

### Python

```py
# Requires Insights preview access for the organization.
sending_domain = None
for domain in client.email.inbox_insights.domains.list(search="mail.example.com"):
    if domain.domain == "mail.example.com":
        sending_domain = domain.domain
        break
if sending_domain is None:
    raise ValueError("Verify mail.example.com in this workspace first")
report = client.email.inbox_insights.blocklists(sending_domain=sending_domain)
print(report.model_dump_json())
```

### Go

```go
// Requires Insights preview access for the organization.
client, err := bird.NewClient(option.WithAPIKey(os.Getenv("BIRD_API_KEY")))
if err != nil {
	log.Fatal(err)
}
ctx := context.Background()
sendingDomain := ""
for domain, err := range client.Email.InboxInsights.Domains.List(ctx, bird.EmailInboxInsightsDomainsListParams{Search: "mail.example.com"}) {
	if err != nil {
		log.Fatal(err)
	}
	if domain.Domain != nil && *domain.Domain == "mail.example.com" {
		sendingDomain = *domain.Domain
		break
	}
}
if sendingDomain == "" {
	log.Fatal("Verify mail.example.com in this workspace first")
}
report, err := client.Email.InboxInsights.Blocklists(ctx, bird.EmailInboxInsightsBlocklistsParams{SendingDomain: sendingDomain})
if err != nil {
	log.Fatal(err)
}
encoded, err := json.MarshalIndent(report, "", "  ")
if err != nil {
	log.Fatal(err)
}
fmt.Println(string(encoded))
```

### PHP

```php
// Requires Insights preview access for the organization.
$sendingDomain = null;
foreach ($bird->email->inboxInsights->domains->list(['search' => 'mail.example.com']) as $domain) {
    if ($domain->getDomain() === 'mail.example.com') {
        $sendingDomain = $domain->getDomain();
        break;
    }
}
if ($sendingDomain === null) {
    throw new \RuntimeException('Verify mail.example.com in this workspace first');
}
$report = $bird->email->inboxInsights->blocklists(['sending_domain' => $sendingDomain]);
var_dump($report);
```

### CLI

```sh
bird email inbox-insights blocklists <sending-domain>
```

### cURL

```sh
curl -X GET "https://us1.platform.bird.com/v1/email/inbox-insights/blocklists" \
  -H "Authorization: Bearer $TOKEN" \
  --url-query "sending_domain=mail.acme.com"
```

## Example response `200`

```json
{
  "resource": "placement",
  "domain": "mail.acme.com",
  "measurement": {
    "sources": [
      "panel",
      "intelliseed_public"
    ],
    "weighting": {
      "weight_set_id": "12",
      "source": "account",
      "basis": "weighted-mean-of-per-isp-rates"
    }
  },
  "generated_at": "2026-08-18T09:34:00Z",
  "freshness": {
    "as_of": "2026-08-17",
    "lag_hint": "daily"
  },
  "cached_at": "2026-08-18T09:40:02Z",
  "active_count": 0,
  "targets": [
    {
      "target": "147.253.40.18",
      "target_type": "ip",
      "is_listed": false,
      "status": "ok",
      "checked_at": "2026-08-20T09:12:04Z",
      "listings": [
        {
          "is_active": false,
          "reason_code": "CSS",
          "provider": "Spamhaus CSS",
          "reason": "Automated listing of a suspected snowshoe range",
          "first_detected": "2026-07-31T00:00:00Z",
          "last_detected": "2026-08-04T00:00:00Z"
        }
      ]
    }
  ]
}
```

## Query parameters

- `sending_domain` (string): The sending domain to check: one of the workspace's verified sending domains, exactly as it appears there. Every sending IP behind it is checked. A domain that is not verified in this workspace answers not-found.

## Response body

- `resource` (string, required): Which resource this response is, echoed for self-description.
- `domain` (string, required): The sending domain the figures describe.
- `measurement` (object): How the figures were measured. Present only where a figure was weighted or drawn from a named set of sources, which today means placement and the industry benchmark. Absent on the reputation resources and on a live lookup, neither of which weights anything.
- `measurement.sources` (array of string, required): Identifiers of the measurement systems that contributed to these figures. The set grows as measurement coverage does, so treat the values as labels rather than a closed list.
- `measurement.weighting` (object): How the figures were weighted. Present on figures weighted against an audience mix, which is placement's method; measurements that weight nothing carry no weighting block.
- `measurement.weighting.weight_set_id` (string, required): The measurement's own identifier for the audience mix, carried through so a client can tell two weightings apart without comparing `basis` strings. No operation accepts it.
- `measurement.weighting.source` (nullable string, required): Which audience mix the weighting used. Null when the measurement weighted these figures by a method this API does not model: the enum is closed so that a client can branch on it exhaustively, which means an unfamiliar method has to answer "not one of these" rather than be passed through. `basis` usually still describes the method in words when that happens.
- `measurement.weighting.basis` (nullable string, required): The weighting method behind the rates, as the measurement names it. A slug rather than a sentence, so render it as a label and do not expect it to read as English. Null when the measurement did not state one, which pairs with `source`: both describe the method, so neither can claim to know it when the measurement was silent.
- `generated_at` (string, required): When the measurement service computed these figures.
- `freshness` (object, required): How current the figures are. Freshness differs per resource (authentication data can lag a day or more while blocklist lookups are near real time), so any "as of" label binds from this field, never from a fixed string.
- `freshness.as_of` (nullable string, required): The most recent UTC day the figures include, or null for a live lookup that has no measurement window.
- `freshness.lag_hint` (nullable string, required)

  How far behind real time this resource usually runs. A lowercase
  identifier rather than a display label, so pick your own wording for it,
  and treat the set as open: the measurement names a hint per resource and
  can add one without notice.

  Null when the measurement reports no hint, which several resources do:
  show the figures without an age rather than inventing one.

  Possible values (may grow over time): `daily`, `nightly`, `near_real_time`
- `cached_at` (string): Present when the response was served from a short-lived copy rather than fetched for this request: when that copy was fetched.
- `active_count` (nullable integer, required): How many of the checked targets currently carry an active listing. A count of targets, not of listings: a target on three blocklists counts once. Null when no target could be checked at all, which is not the same as zero. Zero means every target was checked and none of them is listed.
- `targets` (array of object, required): One entry per sending IP or domain checked for this sending domain.
- `targets.target` (string, required): The sending IP or domain that was checked.
- `targets.target_type` (nullable string, required)

  Whether this target is an IP address or a hostname. Null when the measurement did not report a kind for it, which is possible on a target whose check did not complete.

  Possible values (may grow over time): `ip`, `domain`
- `targets.is_listed` (boolean, required): Whether the target is on at least one blocklist right now. Meaningful only when `status` is `ok`: on any other status this target was not checked, so the value carries no finding either way.
- `targets.status` (string, required)

  Whether this target was actually checked. `unavailable` means the lookup failed or timed out for this target while others may have succeeded, so the honest rendering is "could not check" rather than a result.

  Possible values: `ok`, `no_data`, `not_configured`, `unavailable`, `not_applicable`
- `targets.checked_at` (nullable string, required): When this target was looked up, or null when it was not. Per target rather than per response, because each is a separate live lookup.
- `targets.listings` (array of object, required): Listings seen against this target, including ones that have since cleared, so a recent history is visible even when nothing is active. Read each listing's `is_active` rather than assuming every entry is current.
- `targets.listings.is_active` (boolean, required): Whether this listing is in force now. A false entry is history: it shows the target was listed and has since cleared, which is why the target's `is_listed` can be false while listings are present.
- `targets.listings.reason_code` (nullable string, required): The provider's own short code for the listing reason, or null when it gives none. Stable where the prose in `reason` is not, so branch on this and display that.
- `targets.listings.provider` (string, required): The blocklist that carries the listing. Providers publishing several lists are reported per list rather than under one combined name, because what a listing means and how it is cleared differ per list.
- `targets.listings.reason` (nullable string, required): The reason the provider gives for the listing, or null when it publishes none.
- `targets.listings.first_detected` (string, required): When this listing was first observed.
- `targets.listings.last_detected` (nullable string, required): When this listing was most recently observed, or null while the listing is still in force. A provider records a last sighting only once one exists, so a null here reads as "still listed" rather than "never seen".

## Related resources

- [Should I use a Bird SDK or call the API directly?](/explained/platform/should-i-use-an-sdk-or-call-the-api-directly) (answer)
- [Build your first integration](/learn/paths/integration) (course)
- [Send your first email](/docs/get-started/send-your-first-email) (docs)

[Get an implementation brief](/learn/workspace?topic=api-basics)
