# Company News Signals: GDELT Headlines, Funding & Risk Alerts (`codeclouds/company-news-signals`) Actor

Track global press mentions of any company from GDELT: recent headlines, funding/MA/hiring/legal signal types, match confidence and what is new since the last run. No API key, 65+ languages.

- **URL**: https://apify.com/codeclouds/company-news-signals.md
- **Developed by:** [Dennis](https://apify.com/codeclouds) (community)
- **Categories:** News, Lead generation
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 company profiles

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Company News Signals — GDELT headlines, funding and risk alerts for any company

**What the world press wrote about your companies, classified and compared with the previous run.**

Most "news monitor" actors on Apify scrape Google News, which depends on a search results page that
Google does not license for automated extraction. This actor reads
[GDELT](https://www.gdeltproject.org/), the open global news index that universities, journalists and
researchers query every day. No API key, no login, no scraping of a commercial search engine, and
coverage of roughly 65 languages and more than 100 countries — including local and business press
that an English-language news scraper never sees.

For every company you list, the actor returns the recent headlines, labels each one with a **signal
type** (funding, acquisition, hiring, layoff, leadership, product, partnership, expansion, legal,
regulation, earnings), grades **how reliable the match is**, and — when you schedule the actor —
reports **what is new since the previous run**.

### What it does

- **Headlines per company** from GDELT, with title, URL, source domain, language, source country and an
  ISO-8601 timestamp of the moment GDELT first saw the article (`seendate`, plus the untouched
  `seendateRaw`).
- **Signal classification** per headline, using a deterministic keyword table. No LLM, no external
  classifier: the same headline always yields the same label, so results are stable across runs and
  free of per-run cost. Headlines that fit no signal type are counted separately (`unclassifiedCount`)
  instead of polluting the breakdown.
- **Precision first**: GDELT matches on article *text*, so a query for "Stripe" also returns headlines
  about a zebra crossing or a road marking. This actor only keeps a headline when it actually names
  the company — or, for a distinctive multi-word name such as "Acme Corp", marks it explicitly as a
  text-only match. Every headline carries a **match confidence** and, where relevant, a `matchNote`.
- **Change tracking** between runs: new headlines per company, a changed signal mix, and readable
  summaries such as *"3 new headline(s) about Stripe since 2026-10-02 (last 7d)"*. Already-known
  headlines stay known even when you change a filter between runs.
- **Where the coverage comes from**: the source domains that published the most (`topDomains`), plus
  every language and country the coverage spans.
- **Honest statuses**: `ok`, `no_articles` (looked up, nothing found), `skipped` (over the per-run cap
  or the charge limit) and `error` (GDELT refused or timed out). Nothing disappears silently, and
  nothing found is free.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `companies` | string\[] | 4 examples | **Required.** One company per line, as a name (`Shopify`) or a website address (`stripe.com`). A website address also raises match confidence. A single comma-separated string works too, which is what most agents send. |
| `timespan` | string | `1d` | How far back to look: `1d`, `7d`, `30d`, `90d`. Use `1d` on a schedule, `30d` or `90d` for research. |
| `batchSize` | integer | `1` | Companies per request. `1` (default) keeps attribution exact. A higher value is faster for long lists but shares one 250-headline budget and requires the company name to appear in the headline. |
| `languages` | string\[] | `[]` | Optional source-language filter, e.g. `English`, `Dutch`, `German`. Empty means all languages. |
| `signalTypes` | string\[] | `[]` | Optional filter on the classified signal type, e.g. `["funding", "acquisition", "legal"]`. |
| `maxArticlesPerCompany` | integer | `50` | Keeps at most this many headlines per company, newest first (GDELT maximum is 250). |
| `maxCompanies` | integer | `20` | Deliberate cap: GDELT allows about one request per 20 seconds. Companies above the cap are reported as `skipped` with an explanation, never dropped silently. |
| `classifySignals` | boolean | `true` | Turn signal classification on or off. |
| `trackChanges` | boolean | `true` | Store the headlines seen per company so the next run reports what is new. |
| `onlyChanges` | boolean | `false` | Only return companies that changed since the previous run. |

Domain names are convenient for match confidence, but they cannot restore a brand's exact spelling:
`openai.com` becomes `Openai` in the `company` field (the `domain` field stays `openai.com`, and the
`input` field keeps exactly what you passed). Pass the proper name — `OpenAI` — when branding matters.

### Output example

```json
{
  "company": "Anthropic",
  "input": "Anthropic",
  "domain": null,
  "status": "ok",
  "matchConfidence": "medium",
  "matchWarning": "\"Anthropic\" is a single word, so a headline mentioning it can still be about something else (matchConfidence=medium). Pass the company domain instead of the bare name (e.g. \"example.com\") to grade matches on the source domain.",
  "articleCount": 6,
  "newArticleCount": 2,
  "newestSeendate": "2026-10-02T09:45:00Z",
  "oldestSeendate": "2026-10-01T15:45:00Z",
  "articles": [
    {
      "title": "Anthropic Targets Pre - Thanksgiving IPO at $2 Trillion Valuat ...",
      "url": "https://pymnts.com/.../anthropic-ipo/",
      "domain": "pymnts.com",
      "language": "English",
      "sourceCountry": "United States",
      "seendate": "2026-10-02T09:45:00Z",
      "seendateRaw": "20261002T094500Z",
      "signalType": "funding",
      "matchedKeyword": "ipo",
      "matchedTerm": "Anthropic",
      "matchedCompany": "Anthropic",
      "matchConfidence": "medium",
      "matchNote": null
    }
  ],
  "signalBreakdown": { "funding": 1, "legal": 1 },
  "unclassifiedCount": 4,
  "signals": [
    "2 new headline(s) about Anthropic since 2026-10-02 (last 7d).",
    "1 funding mention(s).",
    "1 legal mention(s).",
    "Most coverage: pymnts.com (1), insurancejournal.com (1).",
    "Coverage in United States, Turkey, Ireland."
  ],
  "topDomains": [
    { "domain": "insurancejournal.com", "count": 1 },
    { "domain": "pymnts.com", "count": 1 }
  ],
  "domains": ["dunyanews.tv", "hurriyetdailynews.com", "insurancejournal.com", "pymnts.com"],
  "languages": ["English"],
  "sourceCountries": ["United States", "Turkey", "Ireland"],
  "isNewCompany": false,
  "changedSincePreviousRun": true,
  "previousRunAt": "2026-10-02T07:00:00.000Z",
  "truncated": true,
  "truncationWarning": "Truncated: GDELT returns at most 250 headlines per query and offers no way to page further back, so this company may have more coverage than shown.",
  "scrapedAt": "2026-10-03T12:00:00.000Z"
}
```

Statuses: `ok` (headlines found), `no_articles` (checked, nothing in the window — the `message` says
which), `skipped` (over `maxCompanies` or the per-run charge limit) and `error` (GDELT refused or timed
out). Only the first one with headlines is billed.

A free `RUN_SUMMARY` record (key-value store) reports how many companies were delivered, skipped or
errored, how many GDELT requests were made, how many were rate-limited and retried, and the signal
totals of the run.

### Match confidence, explained

GDELT returns articles whose *body* contains your search term, but only the headline, URL and source
domain are public. So attribution is graded instead of assumed, and text-only matches are not passed
off as confirmed mentions:

| Grade | When |
|---|---|
| `high` | The article comes from the company's own domain (requires a domain in the input), or the input is a distinctive multi-word name such as "Acme Corp". |
| `medium` | A single-word name such as "Stripe" or "Anthropic": the headline does contain the name, but a headline mentioning one word can still be about something else. Or: a multi-word name matched only in the article text — those articles carry `matchNote`. |
| `low` | Not used for kept articles: too weak to attribute. |

The rule that matters in practice: **for a single-word company name, only headlines that actually
name the company are returned.** Measured on a live query for "Stripe", the text-only matches
included a Zagreb tram announcement, a German car crash and Ukrainian bridge strikes — all correct
matches for the word "stripe", all useless as company news.

In batched mode (`batchSize > 1`) an article is only attributed when its headline, URL or source
domain literally names one of the batch's companies, so `matchConfidence` there reflects the name
match rather than GDELT's full-text hit.

### Use cases

- **Lead generation**: a scheduled daily run over your prospect list surfaces companies that just
  raised money, were acquired, appointed a new CEO or got into legal trouble — before the news
  reaches your inbox.
- **Competitive intelligence**: which competitors launched products, opened offices or changed
  leadership, per week, with the source domains to verify.
- **Recruiting and market research**: hiring and layoff signals per company, optionally restricted
  to one source language (`languages`), from local press instead of only global outlets.
- **Sales-trigger alerts**: `signalTypes: ["funding", "acquisition"]` plus `trackChanges` gives a
  clean feed of companies that recently moved.
- **AI agents**: one call returns structured, already-classified news metadata per company, with no
  article bodies to parse and no keys to configure.

### Pricing

Pay per event, charged only for results you receive:

| Event | Price | Charged when |
|---|---|---|
| `company-profile` | $0.004 | Per company that returned at least one headline |
| `signal-detected` | $0.003 | Per company with at least one headline new since the previous run |

Companies without coverage, companies above `maxCompanies`, and failed requests are free.

### When should an AI agent use this?

- "What has the press written about Stripe, Shopify and Datadog in the last 24 hours?"
- "Which of these 15 companies had new funding or acquisition news this week?"
- "Give me the headlines about Anthropic in English only, classified by signal type."
- "Which companies in my list had leadership changes in the past month?"
- "Monitor these domains daily and give me only the ones that changed."
- "Which of my competitors were in the news for legal or regulatory reasons this week?"

### Legal and data handling

- **Source**: GDELT Project — an open, free, academic news index with a public API designed for
  research and journalistic use. No API key, no account, no login, no anti-bot circumvention.
- **Metadata only**: this actor retrieves headline, URL, source domain, language, country and
  timestamp. It never retrieves or redistributes article text, so it stays clear of reproduction
  rights in the articles themselves.
- **No personal profiles**: headlines may name individuals, and they are returned exactly as
  published. The actor does not build profiles of natural persons, does not collect author lists or
  e-mail addresses, and its own derived fields describe companies and sectors only.
- **Attribution**: every record carries the source domain and URL so results can be verified at the
  original publisher. Defamation risk from a false headline match is the caller's responsibility;
  `matchConfidence` and `matchWarning` exist to surface that risk.
- **Respect the source's limits**: GDELT allows roughly one request every 5 seconds; this actor waits
  at least 20 seconds between requests, backs off on HTTP 429 and serialises all calls, so it stays
  well inside that envelope even on Apify's shared platform.

### Related Actors

- [Strange News Monitor](https://apify.com/CodeClouds/vreemd-nieuws-monitor) — the same GDELT source
  read from the other side: instead of "what happened to this company", it returns the strangest
  headlines of the week (from GDELT, Rechtspraak.nl and KNMI).
- [Tech Stack Detector](https://apify.com/CodeClouds/tech-stack-detector) — the same company,
  different layer: which technologies and internal SaaS tools a domain runs on, with change tracking.
  Combine both for a fuller company profile.
- [Career Sites ATS Job Feed](https://apify.com/CodeClouds/career-sites-ats-jobfeed) — hiring signals
  from company career pages, with hiring profiles and momentum scores.
- [Shopify Price Monitor](https://apify.com/CodeClouds/shopify-price-monitor) — product and price
  changes on stores, another way to detect that a company is moving.

### Limitations

- **Coverage depends on GDELT**, which indexes mostly English, Spanish, Portuguese, Arabic, Russian,
  Chinese and other major languages; very small markets and local-only press may be missing.
- **GDELT is a shared public service** and enforces roughly one request per 5 seconds (in practice
  closer to 20). When it throttles a request despite retries, that company is reported with
  `status: "error"` and the reason; other companies in the run are unaffected. Check `RUN_SUMMARY` for
  `rateLimitedResponses`.
- **Only the last three months** are searchable, and **at most 250 headlines come back per company**.
  That cap is absolute: GDELT's `from` offset parameter is ignored (measured: `from=250` and `from=500`
  return the same newest 250 articles), so older coverage cannot be paged in. Both limits are reported
  in the output (`truncated`, `truncationWarning`).
- **Name matching is textual**, not entity resolution. Give the company domain (`stripe.com` instead
  of `Stripe`) for the highest match confidence.
- **No article bodies**: if you need full text, use a dedicated article-extraction actor for the URLs
  this one returns.

### Keywords

company news monitor, GDELT API, news signals for companies, funding round detection, merger and
acquisition watch, business press tracker, competitive intelligence news, lead generation news
signals, press watch, GDELT news scraper, bedrijfsnieuws monitor, nieuws signalen bedrijf,
pressemonitor, ondernemingsnieuws, financieringsronde detectie, nieuws API.

### Changelog

#### v0.1 — initial release

- Company list input with name-or-domain parsing, duplicate removal and a documented per-run cap. A
  single comma-separated string is accepted too, which is the shape most agents and API clients send.
- GDELT DOC 2.0 integration: keyless, rate-limit-aware (20 s minimum interval, exponential backoff
  with jitter on HTTP 429 *and* on network timeouts), serialized requests.
- Deterministic signal classification into 12 signal types with the matched keyword as evidence;
  unclassified headlines are counted separately instead of blurring the breakdown.
- Precision-first attribution: for a single-word company name only headlines that name the company are
  kept, with match confidence and per-article `matchNote` where a text-only match is retained for a
  multi-word name.
- Optional source-language filter (`languages`).
- Change tracking per company via a named key-value store, with `onlyChanges` for alert feeds. The
  stored key set grows monotonically, so changing a filter does not make known headlines "new" again.
- Honest statuses (`ok`, `no_articles`, `skipped`, `error`), duplicate-headline removal, ISO-8601
  timestamps, `topDomains`, truncation and coverage reporting, and a free `RUN_SUMMARY`.

# Actor input Schema

## `companies` (type: `array`):

One company per line, as a name or a website address (a single comma-separated string works too, which is what most agents and API clients send). Names are matched as a phrase; a website address (e.g. stripe.com) is also used to grade how reliable each headline match is. Note: a website address derives the display name from the domain (openai.com becomes Openai) — pass the proper name instead (OpenAI) if exact branding matters. Duplicates are removed.

## `timespan` (type: `string`):

GDELT keeps about three months of coverage and returns at most 250 headlines per company, newest first. Use 1d for a daily monitor, 7d for a weekly one, 30d or 90d for research.

## `batchSize` (type: `integer`):

1 (default) queries each company separately, which keeps attribution exact. A higher value combines companies into one request and is faster, but GDELT returns at most 250 headlines per request, so busy companies can push quieter ones out of the result and a headline is only assigned when it literally names the company.

## `languages` (type: `array`):

Optional filter on the language of the source article, e.g. English, Dutch, German, French, Spanish, Italian, Portuguese. Leave empty for all languages.

## `signalTypes` (type: `array`):

Optional filter on the classified signal type: funding, acquisition, hiring, layoff, leadership, product, partnership, expansion, legal, regulation, earnings, other.

## `maxArticlesPerCompany` (type: `integer`):

Keeps at most this many headlines per company, newest first. GDELT returns at most 250 per request and offers no way to page further back, so anything above 250 has no effect.

## `maxCompanies` (type: `integer`):

GDELT allows about one request per 20 seconds, so a run is deliberately limited. Companies beyond this limit are reported with status 'skipped' and an explanation instead of being dropped silently.

## `classifySignals` (type: `boolean`):

Labels every headline as funding, acquisition, hiring, layoff, leadership, product, partnership, expansion, legal, regulation, earnings or other, using a deterministic keyword table.

## `trackChanges` (type: `boolean`):

Stores the headlines seen per company so the next run reports which ones are new. The first run creates the baseline; most useful on a schedule.

## `onlyChanges` (type: `boolean`):

Skip companies whose headlines are unchanged since the previous run (the first run still returns everything). Ideal for daily new-signal alerts.

## Actor input object example

```json
{
  "companies": [
    "stripe.com",
    "anthropic.com",
    "Shopify",
    "Datadog"
  ],
  "timespan": "1d",
  "batchSize": 1,
  "languages": [],
  "signalTypes": [],
  "maxArticlesPerCompany": 50,
  "maxCompanies": 20,
  "classifySignals": true,
  "trackChanges": true,
  "onlyChanges": false
}
```

# Actor output Schema

## `results` (type: `string`):

One dataset item per company, including companies that were skipped or returned nothing.

## `runSummary` (type: `string`):

Companies delivered, skipped and errored, GDELT requests made, rate-limited responses and the signal totals of the run.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "companies": [
        "stripe.com",
        "anthropic.com",
        "Shopify",
        "Datadog"
    ],
    "languages": [],
    "signalTypes": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("codeclouds/company-news-signals").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "companies": [
        "stripe.com",
        "anthropic.com",
        "Shopify",
        "Datadog",
    ],
    "languages": [],
    "signalTypes": [],
}

# Run the Actor and wait for it to finish
run = client.actor("codeclouds/company-news-signals").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "companies": [
    "stripe.com",
    "anthropic.com",
    "Shopify",
    "Datadog"
  ],
  "languages": [],
  "signalTypes": []
}' |
apify call codeclouds/company-news-signals --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,codeclouds/company-news-signals"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/4vnFYIj9fyViKseIK/builds/fjTW8hNNdV7OhYSxl/openapi.json
