Skip to main content

Developer Guide

Company Enrichment API: Enrich Any Domain With Signals

POST a domain to /v1/companies/enrich and get back every signal we've detected on that company — news, hiring, funding, SEC filings, social, and more. One request, filterable by type and date.

What This Endpoint Does (and What It Doesn't)

A lot of “company enrichment” APIs return a static profile: employee count, industry code, a revenue bucket. Useful, but it's a snapshot, and snapshots go stale.

Autobound's Signal API enrichment endpoint is built around a different question: what has happened at this company, and when? You send a domain, and you get back the company's signal history — timestamped events across 32 signal types, from funding filings and SEC documents to hiring trends, product launches, and social posts.

One honest caveat up front: this is a signal API, not a firmographics API. The response includes a company object, but firmographic fields like industries and employee_count are frequently empty. Treat the domain as the stable identifier and lean on the signals, which is where the value is. For a broader comparison of enrichment approaches, see our data enrichment API guide.

Quickstart: Your First Enrichment Call

Grab an API key from the developer docs. If you haven't signed up, create a free account — you get 1,000 credits, no credit card.

Enrichment is a POST to /v1/companies/enrich. Authentication is a single X-API-KEY header (an Authorization: Bearer <key> header works too). Here's the smallest useful request:

curl -X POST "https://signals.autobound.ai/v1/companies/enrich" \
  -H "X-API-KEY: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "stripe.com",
    "limit": 5
  }'

A response looks like this (trimmed to a few signals for readability):

{
  "company": {
    "name": "Stripe",
    "domain": "stripe.com",
    "linkedin_url": "https://linkedin.com/company/stripe",
    "industries": [],
    "employee_count_low": 0,
    "employee_count_high": 0
  },
  "signals": [
    {
      "signal_id": "sig_9f2a1c",
      "signal_type": "news",
      "signal_subtype": "companyNews",
      "signal_name": "",
      "detected_at": "2026-06-28T14:02:00Z",
      "association": "company",
      "company": { "name": "Stripe", "domain": "stripe.com" },
      "contact": null,
      "data": {
        "summary": "Stripe expanded stablecoin payment support across 100+ countries.",
        "url": "https://stripe.com/newsroom/news",
        "title": "Stripe expands stablecoin payments"
      }
    },
    {
      "signal_id": "sig_71b0e4",
      "signal_type": "hiring-trends",
      "signal_subtype": "departmentGrowth",
      "signal_name": "",
      "detected_at": "2026-06-21T00:00:00Z",
      "association": "company",
      "company": { "name": "Stripe", "domain": "stripe.com" },
      "contact": null,
      "data": {
        "summary": "Open engineering roles up sharply quarter over quarter."
      }
    },
    {
      "signal_id": "sig_44c8aa",
      "signal_type": "github",
      "signal_subtype": "release",
      "signal_name": "",
      "detected_at": "2026-06-18T09:30:00Z",
      "association": "company",
      "company": { "name": "Stripe", "domain": "stripe.com" },
      "contact": null,
      "data": {
        "summary": "New major release published to a public Stripe repository."
      }
    }
    // ...2 more signals (limit was 5)
  ],
  "signal_summary": {
    "news": 2,
    "hiring-trends": 1,
    "github": 1,
    "product-reviews": 1
  },
  "coverage": {
    "total_before_filters": 63,
    "total_after_filters": 63,
    "signal_types_available": [
      "news", "hiring-trends", "github",
      "product-reviews", "financials", "seo-traffic"
    ],
    "date_range": {
      "earliest": "2025-08-14T00:00:00Z",
      "latest": "2026-06-28T14:02:00Z"
    }
  }
}

We asked for limit: 5, so the signals array is capped at five, but coverage tells you the company actually has 63 signals on file spanning August 2025 to June 2026. At 2 credits per returned signal, this five-signal call cost 10 credits. Raise the limit (up to 500) to pull more history in one shot.

Response Schema, Field by Field

The response has four top-level keys: company, signals, signal_summary, and coverage.

company

The company profile: name, domain, linkedin_url, industries, employee_count_low, and employee_count_high. The last three are optional and often empty — don't build logic that assumes they're populated. Note that linkedin_url may come back as a relative path (linkedin.com/company/stripe) or a full URL, so normalize before comparing or storing.

signals[]

The core of the response. Every element is a signal record with a consistent envelope:

  • signal_id — unique ID for the signal (e.g. sig_abc123). Use it with GET /v1/signals/:id to refetch a single signal.
  • signal_type — one of the 32 canonical slugs (see the catalog below).
  • signal_subtype — a finer-grained label, e.g. companyNews.
  • signal_name — a human-readable title, but frequently empty. When it is, fall back to signal_subtype or data.summary.
  • detected_at — ISO-8601 timestamp of when the event was detected. This is your recency dimension.
  • association "company" or "contact".
  • company and contact — the entities attached to the signal (contact is null for company-level signals).
  • data — the type-specific payload. Its shape varies by signal type, so treat it as an open object. A data.summary string is your most reliable field for rendering a one-liner; inspect the rest at runtime rather than hardcoding keys.

signal_summary

A count of signals by type in this response, e.g. { "news": 2, "hiring-trends": 1 }. Handy for a quick “what kind of activity is this account showing” readout without iterating the array.

coverage

The envelope that tells you what exists beyond what you pulled: total_before_filters (all signals for the company), total_after_filters (how many matched your signal_types and detected_after filters), signal_types_available (which types have at least one row for this company), and date_range (earliest and latest detection). Use signal_types_available to decide whether a second, filtered call is worth making.

The 32 Signal Types — and How to Filter Them

You rarely want everything. Pass a signal_types array to return only the slugs you care about, and detected_after to bound the window. Filtering narrows the response and lowers cost, since you're billed per signal returned. Here are all 32 slugs, grouped for readability (the groupings are ours; the API has no category field). Types marked contact attach to people rather than companies — they surface through contact enrichment.

Growth & hiring

Headcount momentum, job posting velocity, and career moves.

hiring-trendshiring-velocityemployee-growthwork-milestonescontact

Funding & financial

Private raises, earnings events, and financial performance.

sec-form-d-fundingfinancialsearnings-transcripts

SEC filings

Public-company disclosures pulled from EDGAR.

sec-10ksec-10qsec-8ksec-6ksec-20f

News, PR & IP

Press coverage, launches, awards, filings, and patents.

newsproducthunt-launchfederal-contract-awardnew-business-formationpatents

Social & community

Owned and earned activity across the major platforms.

linkedin-post-companylinkedin-post-contactcontactlinkedin-comments-contactcontacttwitter-company-poststwitter-contact-postscontactreddit-companyyoutube-companyyoutube-contactcontacthackernewsconference-speakercontact

Product & web

Engineering activity, reviews, traffic, and site changes.

githubproduct-reviewsseo-trafficwebsite-intelligenceglassdoor-company

A filtered call — only funding, news, and hiring, only since May 1st:

curl -X POST "https://signals.autobound.ai/v1/companies/enrich" \
  -H "X-API-KEY: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "stripe.com",
    "signal_types": ["news", "sec-form-d-funding", "hiring-trends"],
    "detected_after": "2026-05-01T00:00:00Z",
    "limit": 50
  }'

Legacy SEC slugs (10k, 10q, 8k) and other registered aliases resolve server-side to their canonical values, so an old integration won't silently break. For what each family of signals means for outreach, see the trigger events guide plus our hiring signals and SEC filings breakdowns.

Credits, Rate Limits, and Errors

Enrichment costs 2 credits per signal returned, billed per row. A response with 8 signals costs 16 credits. A 404 (company not found) or a filtered call that matches nothing is never charged — you only pay for data you actually receive. Every request returns credit accounting in the headers:

  • x-credits-charged — credits charged for this request
  • x-credits-remaining — your running balance
  • x-credits-action-cost — the per-action rate used

The default rate limit is 300 requests per minute per account. When you hit it you get a 429 with a Retry-After header — back off for that many seconds and retry. There is no bulk endpoint; to enrich a list, parallelize single calls within that budget (the Python pattern below does exactly that).

The other status codes worth handling:

  • 400 — invalid request (e.g. no identifier supplied)
  • 401 — missing or bad API key
  • 402 — credits exhausted
  • 404 — no signals for this company

What it costs at each credit pack

Credits are a one-time purchase that never expires, and every pack includes all 35+ sources. Using an ~8-signal average per company (16 credits):

PackPriceCredits~Companies (8 signals each)
Starter$192,000~125
Growth$495,444~340
Scale$14919,867~1,240
Pro$49983,167~5,200

Larger Business and Enterprise packs run down to $0.00348/credit, and for 100K+ records on a regular cadence, flat-file licensing is usually cheaper than the API. Full breakdown at autobound.ai/pricing. Filtering with signal_types and detected_after is the simplest way to keep spend down: fewer, more relevant signals per call.

Three Patterns Teams Ship in a Day

Pattern 1: A clean single-domain helper

The building block for everything else. Note the 404 handling (free, not an error worth retrying) and the fallback chain for rendering a signal title.

import requests

API_KEY = "YOUR_API_KEY"
URL = "https://signals.autobound.ai/v1/companies/enrich"

def enrich(domain, signal_types=None, detected_after=None, limit=50):
    body = {"domain": domain, "limit": limit}
    if signal_types:
        body["signal_types"] = signal_types
    if detected_after:
        body["detected_after"] = detected_after

    resp = requests.post(
        URL,
        headers={"X-API-KEY": API_KEY, "Content-Type": "application/json"},
        json=body,
        timeout=30,
    )

    if resp.status_code == 404:
        return None  # no signals for this company — you were not charged
    resp.raise_for_status()

    # Credit accounting lives in the response headers
    print("charged:", resp.headers.get("x-credits-charged"),
          "remaining:", resp.headers.get("x-credits-remaining"))
    return resp.json()

data = enrich("stripe.com", signal_types=["news", "hiring-trends"])
for s in (data or {}).get("signals", []):
    # signal_name is often empty — fall back to subtype or data.summary
    title = s.get("signal_name") or s.get("signal_subtype") or s["data"].get("summary")
    print(s["signal_type"], s["detected_at"], "-", title)

Pattern 2: “Bulk” enrichment by parallelizing

There's no batch endpoint, so enrich a CSV by running single calls concurrently while respecting the 300/minute limit. This handles 429 with the Retry-After header and skips unmatched domains for free.

import concurrent.futures as cf
import csv, time, requests

API_KEY = "YOUR_API_KEY"
URL = "https://signals.autobound.ai/v1/companies/enrich"
# Account limit is 300 requests/minute. A handful of workers with a small
# delay keeps you comfortably under it.
MAX_WORKERS = 4

def enrich(domain):
    resp = requests.post(
        URL,
        headers={"X-API-KEY": API_KEY, "Content-Type": "application/json"},
        json={"domain": domain, "detected_after": "2026-01-01T00:00:00Z"},
        timeout=30,
    )
    if resp.status_code == 429:
        time.sleep(int(resp.headers.get("Retry-After", "5")))
        return enrich(domain)
    if resp.status_code == 404:
        return domain, None  # unmatched — free
    resp.raise_for_status()
    return domain, resp.json()

with open("accounts.csv") as f:
    domains = [row["domain"] for row in csv.DictReader(f)]

results = {}
with cf.ThreadPoolExecutor(max_workers=MAX_WORKERS) as pool:
    for domain, payload in pool.map(enrich, domains):
        results[domain] = payload
        time.sleep(0.2)  # ~5 req/sec ceiling

matched = sum(1 for v in results.values() if v)
print(f"Enriched {matched}/{len(domains)} domains")

Pattern 3: CRM auto-enrichment on lead creation

When a lead enters Salesforce or HubSpot, a webhook enriches the account and writes the freshest signal plus a type breakdown back to the record. Reps open the account and immediately see what's happening.

// CRM webhook: enrich an account when a new lead is created
async function onLeadCreated(lead) {
  const resp = await fetch("https://signals.autobound.ai/v1/companies/enrich", {
    method: "POST",
    headers: {
      "X-API-KEY": process.env.AUTOBOUND_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      domain: lead.company_domain,
      detected_after: "2026-01-01T00:00:00Z",
      limit: 25,
    }),
  });

  if (resp.status === 404) return; // no signals, nothing to write
  const { signals, signal_summary } = await resp.json();

  // Write the freshest signal + a type breakdown back to the account record
  const top = signals[0];
  await crm.updateAccount(lead.account_id, {
    latest_signal: top?.data?.summary ?? top?.signal_subtype ?? "None",
    latest_signal_at: top?.detected_at ?? null,
    signal_types: Object.keys(signal_summary).join("; "),
    signal_count: signals.length,
  });
}

Bonus: an agent tool for AI SDRs

The same endpoint is exposed through Autobound's MCP server, so an AI SDR can call enrichCompany as a tool, pull the freshest signal, and use data.summary as the personalization hook before a message ever sends. That's the difference between generic personalization and something specific, timely, and sourced — more on that in why AI SDR platforms need a signal data layer.

Where to Go From Here

Enrichment is the “look up a company I already know” call. Its sibling, POST /v1/companies/search, is the “find companies I don't know yet” call — query the database by signal (“raised a Series B in the last 30 days”) and get matching accounts back. Same auth, same credit model.

Browse the full catalog of signal types and volumes at autobound.ai/signal-data (browsing is free — you only spend credits on enrich and search calls). To understand how signal data differs from intent and firmographic data, read the explainer, and for the end-to-end outbound workflow, the B2B prospecting signals guide covers detection through sent email. Teams scoring accounts on signal density should also see lead scoring with signals.

Enrich Your First Domain

1,000 free credits on signup, no credit card. Paste the curl command above, swap in your key, and pull a company's signal history in one request.

Frequently Asked Questions

An array of signals we've detected on the company: news, hiring trends, funding filings, SEC documents, social posts, product reviews, and more, plus a signal_summary count by type and a coverage envelope. Each signal carries a signal_type, signal_subtype, detected_at timestamp, and a type-specific data payload. It is a signal API, not a firmographics API: fields like industry and employee count are frequently empty, so treat the domain as the stable key.

Traditional enrichment APIs return static firmographic fields (employee count, industry, revenue). This endpoint returns time-stamped events: what happened at the company and when. You send a domain and get the signal history, filterable by type and date. Static data tells you what a company is; signals tell you why it matters this week.

At least one of domain (preferred), company_name, or linkedin_url. Domain is the most reliable identifier. You can also pass signal_types to filter, detected_after to bound the date range, and limit (1-500, default 50) to cap how many signals come back.

2 credits per signal returned, billed per row. A response with 8 signals costs 16 credits. Zero-result lookups (company not found, or no signals matching your filters) are never charged. Every signup includes 1,000 free credits, and credits never expire.

No. Enrichment is one domain per request. To enrich a list, parallelize single POST calls while staying under the account rate limit of 300 requests per minute. Because zero-result calls are free, unmatched domains don't burn credits.

There is a separate POST /v1/contacts/enrich endpoint, but contact-level signal coverage is currently thin, so many inputs return an empty result. In practice, resolve the contact to their employer's domain and call /v1/companies/enrich for richer signal history.

Refresh cadence varies by signal type. News and funding (SEC Form D) refresh multiple times a day to daily; hiring, SEC filings, and earnings refresh weekly; most social and product sources refresh monthly. The coverage.date_range field in every response tells you the earliest and latest signal we hold for that company.