# Add exclusions

Adds domains, addresses, company names or LinkedIn profiles nobody may contact, with their type and reason. One that exists gets the new type and reason. A bounce or unsubscribe you log with POST /outreach/activity is added by itself. For a person who opts out of every channel, add their `linkedin_url` (and their address if you have it): an address only marks a row that carries it. The response says which rows of Leads and Companies each exclusion now marks as Do not contact (`marks`), and warns when an address marks none (`warnings`).

## Authentication

Send `Authorization: Bearer gtk_…`. Create the key under **Connect assistant → API keys**. Every `gtk_` key can call these routes, also while the Signals API is closed. A missing, invalid or revoked key is **401**.

### Body parameters

- **`exclusions`** `object[]` · **required** — 1 to 500 items.
- **`exclusions[].domain | email | company | linkedin_url`** `string` · **required** — Exactly one: a domain (`acme.com`; an address gives its domain), an address, a company name for when there is no domain, or one person’s LinkedIn profile URL.
- **`exclusions[].type`** `string` — Default `other`. One of: `customer`, `trial`, `test`, `former_customer`, `pipeline`, `unsubscribed`, `bounced`, `competitor`, `other`.
- **`exclusions[].reason`** `string` — Why, up to 500 characters.
- **`exclusions[].source`** `string` — Where it came from, e.g. "CRM export".

## Response

- **`added`** `integer` · **required** — New exclusions.
- **`updated`** `integer` · **required** — Exclusions that existed and got the new type and reason.
- **`data`** `object[]` · **required** — The saved exclusions, as GET /exclusions gives them, each with `marks`: the rows it now marks as Do not contact (`leads_total`, `companies_total`, and up to 10 of each with `id` and `name`). A bounced address marks nothing.
- **`warnings`** `object[]` — An address that marks no row: `id`, `code` (`marks_no_row`), `message`.
- **`dashboard_url`** `string` · **required** — Companies in mySDR, filtered on Do not contact.

## Credits

Free. This call never uses credits.

## Errors

- **400** `invalid_request` — No `exclusions`, more than 500, an item with none or several of domain, email, company and linkedin_url, an unknown field or type, or a value that is not a domain, address, name or LinkedIn profile.
- **401** `authentication_error` — The Authorization header is missing, or the key is unknown or revoked.
- **403** `permission_error` — API access is not part of your plan, or your account is suspended.
- **429** `rate_limit_error` — More than 60 requests per minute, or more than 3 at the same time, on one key. Wait for the Retry-After header.
- **503** `service_unavailable` — The API is temporarily unavailable. Retry later.
- **500** `internal_error` — Something went wrong on our side.

## Example

### cURL

```bash
curl -X POST "https://mysdr.app/api/v1/exclusions" \
  -H "Authorization: Bearer $MYSDR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "exclusions": [
      {
        "domain": "customer.example",
        "type": "customer",
        "reason": "Customer since 2024"
      },
      {
        "email": "test@beta.example",
        "type": "test"
      }
    ]
  }'
```

### JavaScript

```javascript
const response = await fetch('https://mysdr.app/api/v1/exclusions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.MYSDR_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    "exclusions": [
      {
        "domain": "customer.example",
        "type": "customer",
        "reason": "Customer since 2024"
      },
      {
        "email": "test@beta.example",
        "type": "test"
      }
    ]
  })
});
const body = await response.json();
```

### Python

```python
import json
import os
import urllib.request

payload = {
  "exclusions": [
    {
      "domain": "customer.example",
      "type": "customer",
      "reason": "Customer since 2024"
    },
    {
      "email": "test@beta.example",
      "type": "test"
    }
  ]
}

request = urllib.request.Request(
    "https://mysdr.app/api/v1/exclusions",
    headers={
    "Authorization": f"Bearer {os.environ['MYSDR_API_KEY']}",
    "Content-Type": "application/json"
    },
    data=json.dumps(payload).encode(),
    method="POST",
)
with urllib.request.urlopen(request) as response:
    body = json.load(response)
```

### Example response

```json
{
  "added": 2,
  "updated": 0,
  "data": [
    {
      "id": "exc_9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
      "kind": "domain",
      "value": "customer.example",
      "label": "customer.example",
      "type": "customer",
      "reason": "Customer since 2024",
      "source": null,
      "created_at": "2026-10-23T09:00:00.000Z",
      "marks": {
        "leads_total": 1,
        "companies_total": 1,
        "leads": [
          {
            "id": "201",
            "name": "Jane Doe"
          }
        ],
        "companies": [
          {
            "id": null,
            "name": "Customer"
          }
        ]
      }
    },
    {
      "id": "exc_1b2c3d4e-5f60-4a71-8b82-9c0d1e2f3a4b",
      "kind": "email",
      "value": "test@beta.example",
      "label": "test@beta.example",
      "type": "test",
      "reason": null,
      "source": null,
      "created_at": "2026-10-23T09:00:00.000Z",
      "marks": {
        "leads_total": 0,
        "companies_total": 0,
        "leads": [],
        "companies": []
      }
    }
  ],
  "warnings": [
    {
      "id": "exc_1b2c3d4e-5f60-4a71-8b82-9c0d1e2f3a4b",
      "code": "marks_no_row",
      "message": "No row in Leads or Companies has test@beta.example, so no row is marked Do not contact. If the person is in Leads, add their LinkedIn profile as an exclusion too (linkedin_url), or put the address on their row with update_row (PATCH /tables/leads/{id})."
    }
  ],
  "dashboard_url": "https://mysdr.app/en/gtm/companies?excluded=1"
}
```
