# Enrich Companies

Send exactly one of `linkedin_urls`, `domains` or `names`, with up to 25 entries. Each entry gets its own result, in the order you sent them, with a list of `matches`.

Each entry gives at most one company. A LinkedIn URL gives that page. A domain gives the company on exactly that domain; set `exact_match` to false to also accept a company the source ties to the domain on another one (`confidence_score` is then null). A name gives the company with exactly that name, or else the best match (`confidence_score` 1.0 on an exact name, null otherwise); set `exact_match` to true to keep only exact names. To choose between companies that share a name, use the free GET /companies/lookup and enrich the page you pick.

`fields` picks the groups: `basic_info` (the default), `headcount`, `locations`, `taxonomy`, `followers`, `funding` and `hiring`. A value we cannot fill is null, and its path is listed in `unavailable`, so you can tell "unknown" from "zero".

Every company found costs one credit, whatever groups you ask for; the open roles in `hiring` are included. Not found, invalid, capped or failed entries cost nothing, and the same company asked twice is looked up and charged once, also in two forms (a numeric LinkedIn page and its slug, or a domain and the page of the same company).

Send `Authorization: Bearer gtk_…`. Create the key under **Connect assistant → API keys**. A missing, invalid or revoked key is **401**. A valid key while this API is not enabled for your organization answers **403** `api_not_enabled`, with what the key can do instead. Every response includes `mySDR-Api-Version`, `mySDR-Request-Id` and `Cache-Control: no-store`, and a response to an accepted key also `X-RateLimit-*` (so not a 401, a 405, an OPTIONS or a 404 for an unknown path).

## Jobs

Add `hiring` to `fields` to get the open roles. `company.hiring` gives `openings_count`, the number of open roles on LinkedIn, and `recent_openings`: the newest roles, newest first, each with `title`, `url`, `location` and `posted_on`. The roles cost nothing extra: a company found is 1 credit, whatever groups you ask for. A value we cannot fill is null and listed in `unavailable`.

```json
{
  "linkedin_urls": [
    "https://www.linkedin.com/company/acme-analytics"
  ],
  "fields": [
    "basic_info",
    "hiring"
  ]
}
```

## Authentication

Send your API key as a Bearer token: `Authorization: Bearer gtk_…`. Create the key under **Connect assistant → API keys**. A missing, invalid or revoked key is **401**, including while an API is closed. On an endpoint of the Signals API or the Company API, a valid key while that API is closed for your organization answers **403** `api_not_enabled`, with what the key can do instead; the other endpoints work with every key. Every response includes `mySDR-Api-Version`, `mySDR-Request-Id` and `Cache-Control: no-store`, and a response to an accepted key also `X-RateLimit-*` (so not a 401, a 405, an OPTIONS or a 404 for an unknown path); every error has `type`, `message`, `param` and `request_id`.

### Body parameters

- **`linkedin_urls`** `string[]` — LinkedIn company pages, e.g. `https://www.linkedin.com/company/acme-analytics`.
- **`domains`** `string[]` — Company websites or domains, e.g. `acme-analytics.example`.
- **`names`** `string[]` — Company names.
- **`fields`** `string[]` — Groups to return. Defaults to `["basic_info"]`. One of: `basic_info`, `headcount`, `locations`, `taxonomy`, `followers`, `funding`, `hiring`.
- **`exact_match`** `boolean` — Domains: only a company on exactly that domain (defaults to true). Names: only a company with exactly that name (defaults to false: the best match).
- **`max_credits`** `integer` — Stop before the call costs more than this. An entry is looked up only while its company still fits; an entry that finds nothing costs nothing and leaves the room to the next. Entries left over are `capped`. Below the cost of one company the call is `402` `max_credits_exceeded`.

## Response

- **`results`** `object[]` — One entry per input, in the same order.
- **`results[].input`** `string` — The input as you sent it.
- **`results[].status`** `string` — `matched`, `not_found`, `invalid` (not a LinkedIn company URL, domain or name), `capped` (not looked up: `max_credits` had no room left) or `error` (lookup failed; retry later). One of: `matched`, `not_found`, `invalid`, `capped`, `error`.
- **`results[].match_type`** `string` — `linkedin_url`, `domain` or `name`. One of: `linkedin_url`, `domain`, `name`.
- **`results[].matches`** `object[]` — `confidence_score` and `company`: one company when the status is `matched`, otherwise empty.
- **`results[].matches[].confidence_score`** `number | null` — 1 on a LinkedIn URL, on exactly the domain you sent or on exactly the name you sent; null otherwise (a company on another domain with `exact_match` false, or the best match on a name).
- **`results[].matches[].company`** `object` — The company. See the fields below.
- **`results[].message`** `string` — On `not_found`, `invalid` and `capped`: why, and what to send instead.
- **`company.linkedin_url`** `string | null` — The company page.
- **`company.basic_info`** `object` — `name`, `linkedin_url`, `website`, `primary_domain`, `description`, `tagline`, `logo_url`, `employee_count_range`, `company_type`, `year_founded`.
- **`company.headcount`** `object` — `total` (employees on LinkedIn) and `range`. Growth, role mix and region mix are null and listed in `unavailable`.
- **`company.locations`** `object` — `headquarters`, `city`, `state`, `country`, `country_code`, `street_address`, `postal_code` and `all_office_addresses`.
- **`company.taxonomy`** `object` — `industry` and `specialities`.
- **`company.followers`** `object` — `count`. Follower growth is null and listed in `unavailable`.
- **`company.funding`** `object` — `last_round_type`, `last_round_amount_usd`, `last_fundraise_date`, `investors` and `rounds` (newest first), from the funding rounds we track.
- **`company.hiring`** `object` — `openings_count` (open roles on LinkedIn) and `recent_openings`: the newest roles, with `title`, `url`, `location` and `posted_on`.
- **`company.unavailable`** `string[]` — Paths in the requested groups that this API cannot fill for this company.
- **`credits_used`** `integer` — Credits this call used.
- **`truncated_by`** `string` — Only when `max_credits` left entries `capped`: `max_credits`. One of: `max_credits`.

## Credits

1 credit per company found. Errors and empty lookups are free. A call that spent credits puts that number in `credits_used`, in `X-Credits-Used` and in `X-Credits-Charged`. When the wallet can be read, the response also sends `X-Credits-Remaining`.

## Errors

- **400** `invalid_request` — The body is not valid JSON, a field is unknown or has the wrong type, or a value is not allowed.
- **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.
- **403** `api_not_enabled` — The key is valid, but the Signals API or the Company API is not enabled for your organization yet. The error says what this key can do.
- **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.
- **429** `daily_limit_reached` — The request could take your organization over its daily credit limit. The limit resets at midnight (Europe/Brussels).
- **502** `upstream_error` — The data source did not answer. Retry later.
- **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/company/enrich" \
  -H "Authorization: Bearer $MYSDR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "linkedin_urls": [
      "https://www.linkedin.com/company/acme-analytics"
    ],
    "fields": [
      "basic_info",
      "headcount"
    ]
  }'
```

### JavaScript

```javascript
const response = await fetch('https://mysdr.app/api/v1/company/enrich', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.MYSDR_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    "linkedin_urls": [
      "https://www.linkedin.com/company/acme-analytics"
    ],
    "fields": [
      "basic_info",
      "headcount"
    ]
  })
});
const body = await response.json();
```

### Python

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

payload = {
  "linkedin_urls": [
    "https://www.linkedin.com/company/acme-analytics"
  ],
  "fields": [
    "basic_info",
    "headcount"
  ]
}

request = urllib.request.Request(
    "https://mysdr.app/api/v1/company/enrich",
    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
{
  "results": [
    {
      "input": "https://www.linkedin.com/company/acme-analytics",
      "status": "matched",
      "match_type": "linkedin_url",
      "matches": [
        {
          "confidence_score": 1,
          "company": {
            "linkedin_url": "https://www.linkedin.com/company/acme-analytics",
            "basic_info": {
              "name": "Acme Analytics",
              "linkedin_url": "https://www.linkedin.com/company/acme-analytics",
              "website": "https://acme-analytics.example",
              "primary_domain": "acme-analytics.example",
              "description": "Analytics for finance teams.",
              "tagline": null,
              "logo_url": null,
              "employee_count_range": "51-200",
              "company_type": "Privately Held",
              "year_founded": 2019
            },
            "headcount": {
              "total": 87,
              "range": "51-200",
              "growth_percent": {
                "1m": null,
                "3m": null,
                "6m": null,
                "12m": null
              },
              "growth_absolute": {
                "1m": null,
                "3m": null,
                "6m": null,
                "12m": null
              },
              "by_role": null,
              "by_region": null,
              "largest_headcount_country": null
            },
            "unavailable": [
              "headcount.growth_percent",
              "headcount.growth_absolute",
              "headcount.by_role",
              "headcount.by_region",
              "headcount.largest_headcount_country"
            ]
          }
        }
      ]
    }
  ],
  "credits_used": 1
}
```
