# Get Company Tech Stack

Send `domains` (company websites) and/or `linkedin_urls` (LinkedIn company pages), up to 25 together. Each gets its own entry in `results`, in the order you sent them (domains first). A technology appears when the company’s job posts mention it: `first_seen_on` and `last_seen_on` are the first and the last job post that did, `jobs_mentioning` how many, and `confidence` how sure the match is. A company that rarely hires has no tech stack here.

By default you get the technologies seen in the last 12 months with medium or high confidence, the most certain and most mentioned first, at most 50 per company; `seen_within_months`, `min_confidence` and `limit` change that, and `left_out` says what the defaults hid. To answer "does this company use X?", send `technologies`: `asked` then says yes or no per technology, with the evidence. A name the catalog does not know is `400` `unknown_technology` with the closest names in `suggestions`, before anything is charged. For one kind of tool, send `categories` (e.g. `CRM`, `Marketing Automation`, `ERP`).

Some names are also common words or job titles in job posts ("streamline", "team leader"). A word that is almost never the product is left out unless you ask for it by name (`left_out.common_words` counts them); a real product with such a name is shown with `ambiguous: true`, after the rest. Check its evidence before you rely on it.

A company costs the same whether your filters keep fifty technologies or none: the lookup was made. Every call is charged, also a repeat of the same company, so to answer several questions about one company, ask once without filters and answer from that. Not found, invalid, capped or failed entries cost nothing, and the same company in one call (also its domain and its LinkedIn page) is looked up and charged once. A domain finds a company more often than a LinkedIn page.

## Authentication

Send `Authorization: Bearer gtk_…`. Create the key under **Connect assistant → API keys**. This endpoint answers for every key with API access: it is not behind the Signals API or the Company API. A missing, invalid or revoked key is **401**. Every response includes `mySDR-Api-Version`, `mySDR-Request-Id` and `Cache-Control: no-store`, and a response to an accepted key also `X-RateLimit-*`; every error has `type`, `message`, `param` and `request_id`.

### Body parameters

- **`domains`** `string[]` — Company websites or domains, e.g. `acme.com` (a URL or an email address works too).
- **`linkedin_urls`** `string[]` — LinkedIn company pages, e.g. `https://www.linkedin.com/company/acme-analytics`.
- **`technologies`** `string[]` — Only these technologies, by slug or name, up to 20: "does this company use HubSpot?". `asked` answers per technology. A name the catalog does not know is a `400`, free. Leave it out for the whole stack.
- **`categories`** `string[]` — Only technologies of these kinds, up to 20, e.g. `CRM`, `ERP`, `Marketing Automation` or a whole area such as `Sales` or `HR`. Every word must be in the category or the technology’s name (`CRM` also finds HubSpot CRM and HubSpot). With `technologies` too, a technology must match both.
- **`min_confidence`** `string` — The weakest evidence to include. Defaults to `medium`. One of: `low`, `medium`, `high`.
- **`seen_within_months`** `integer` — Only technologies a job post mentioned in the last this many months, 1 to 120. Defaults to 12.
- **`limit`** `integer` — Technologies per company, 1 to 200. Defaults to 50.
- **`max_credits`** `integer` — Stop before the call costs more than this. A company is looked up only while it still fits; one we do not know costs nothing and leaves the room to the next. Entries left over are `capped`. Below the price of one company the call is `402` `max_credits_exceeded`.

## Response

- **`results`** `object[]` — One entry per company you sent, in the same order.
- **`results[].input`** `string` — The domain or URL as you sent it.
- **`results[].match_type`** `string` — `domain` or `linkedin_url`. One of: `domain`, `linkedin_url`.
- **`results[].status`** `string` — `matched`, `not_found` (no job posts of this company in our data, or a LinkedIn page we could not match), `invalid` (not a company domain or LinkedIn company page; `message` says what to send instead), `capped` (not looked up: `max_credits` had no room left) or `error` (retry later). One of: `matched`, `not_found`, `invalid`, `capped`, `error`.
- **`results[].company`** `object` — `name`, `domain` and `linkedin_url`, as far as we know them.
- **`results[].technologies`** `object[]` — `slug`, `name`, `category`, `parent_category`, `confidence` (`low`, `medium` or `high`: how sure the match is), `first_seen_on` and `last_seen_on` (the first and the last job post that mentioned it) and `jobs_mentioning` (in how many job posts), plus `jobs_last_30_days`, `rank_in_category` (1 = the most mentioned tool of its kind at this company) and `ambiguous` (true when the name is also a common word or job title in job posts).
- **`results[].technologies_total`** `integer` — Every technology we know for this company, before your filters (the data returns its 500 most certain).
- **`results[].technologies_total_capped`** `boolean` — Only when true: the data returned its 500 most certain technologies and knows more, with less evidence.
- **`results[].asked`** `object[]` — Only with `technologies`: one entry per technology you asked for, `technology` as you sent it and `found` true or false; when found, `slug`, `name`, `confidence`, `last_seen_on`, `jobs_mentioning`, `meets_filters: false` when it is there but older or weaker than your filters, and `ambiguous` when its name is also a common word.
- **`results[].technologies_matching`** `integer` — How many passed your filters, before `limit`.
- **`results[].left_out`** `object` — Only when something was hidden: `lower_confidence` and `older` (hidden by `min_confidence` and `seen_within_months`) and `common_words` (common words the data reads as a product, hidden unless you ask for them by name), how many.
- **`results[].observed_at`** `string` — When this tech stack was fetched (ISO 8601); kept up to 30 days.
- **`results[].credits_charged`** `integer` — 3 on the first entry of a company we know, otherwise 0.
- **`results[].message`** `string` — Why an entry has no technologies, and what to send instead.
- **`credits_used`** `integer` — Credits this call used.
- **`truncated_by`** `string` — Only when `max_credits` left entries `capped`: `max_credits`. One of: `max_credits`.
- **`sources`** `string[]` — Always `["job_postings"]`.

## Credits

3 credits per company we know; a company we do not know costs nothing. 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.
- **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).
- **402** `insufficient_credits` — The credit balance is lower than this request could cost. Nothing was charged. Add credits, or send a smaller request. The error includes code, fix and a link to the credits docs.
- **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.
- **402** `max_credits_exceeded` — max_credits is lower than the credits of the first row, so nothing could be returned. Nothing was charged; raise max_credits or ask for fewer rows.

## Example

### cURL

```bash
curl -X POST "https://mysdr.app/api/v1/company/tech-stack" \
  -H "Authorization: Bearer $MYSDR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domains": [
      "acme-analytics.example"
    ],
    "categories": [
      "CRM",
      "Marketing Automation"
    ]
  }'
```

### JavaScript

```javascript
const response = await fetch('https://mysdr.app/api/v1/company/tech-stack', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.MYSDR_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    "domains": [
      "acme-analytics.example"
    ],
    "categories": [
      "CRM",
      "Marketing Automation"
    ]
  })
});
const body = await response.json();
```

### Python

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

payload = {
  "domains": [
    "acme-analytics.example"
  ],
  "categories": [
    "CRM",
    "Marketing Automation"
  ]
}

request = urllib.request.Request(
    "https://mysdr.app/api/v1/company/tech-stack",
    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": "acme-analytics.example",
      "match_type": "domain",
      "status": "matched",
      "company": {
        "name": "Acme Analytics",
        "domain": "acme-analytics.example",
        "linkedin_url": null
      },
      "technologies": [
        {
          "slug": "hubspot",
          "name": "HubSpot",
          "category": "Marketing Automation Platforms",
          "parent_category": "Marketing",
          "confidence": "high",
          "first_seen_on": "2024-03-12",
          "last_seen_on": "2026-10-02",
          "jobs_mentioning": 14,
          "jobs_last_30_days": 2,
          "rank_in_category": 1,
          "ambiguous": false
        }
      ],
      "technologies_total": 61,
      "technologies_matching": 1,
      "observed_at": "2026-10-25T09:12:04.000Z",
      "credits_charged": 3
    }
  ],
  "credits_used": 3,
  "sources": [
    "job_postings"
  ]
}
```
