> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gdeltcloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a monitoring feed

> The developer path: resolve an entity once, then reuse that id across every surface.

One task, end to end: **watch what is happening to a company across news, government awards and
physical assets.** It takes four calls, and the shape of it generalises to almost everything else
you will build here.

The whole design rests on one idea: **resolve a name to an id once, then never resolve it again.**

<Steps>
  <Step title="Resolve the name">
    Never filter by a company name string. Names are ambiguous, they change, and every surface spells
    them differently. Ask the resolver instead:

    ```bash theme={null}
    curl -G "https://gdeltcloud.com/api/v2/search" \
      --data-urlencode "q=Petrobras" \
      --data-urlencode "universe=all" \
      -H "Authorization: Bearer $GDELT_API_KEY"
    ```

    Take the `entity_id` from the best match — an `e_…` spine id. That one string is the join key for
    everything below.

    <Tip>
      An empty result for a valid-looking name usually means the entity genuinely is not in the corpus,
      not that the query is wrong. The resolver returns no match rather than a guess.
    </Tip>
  </Step>

  <Step title="What is happening to it">
    ```bash theme={null}
    curl -G "https://gdeltcloud.com/api/v2/events" \
      --data-urlencode "entity=e_..." \
      --data-urlencode "days=7" \
      --data-urlencode "sort=significance" \
      -H "Authorization: Bearer $GDELT_API_KEY"
    ```

    `sort=significance` is the default and the one you want for monitoring — it ranks by how much the
    event matters, not by how recently it arrived. Sorting by recency gives you a feed dominated by
    whatever a wire service published in the last hour.

    Start wide. The most common mistake on this API is stacking four filters on the first call and
    concluding the data is missing when the empty result is really your `AND`.
  </Step>

  <Step title="Read the evidence">
    An event is a claim; the articles are why we made it. Every event links to the story it came from:

    ```bash theme={null}
    curl "https://gdeltcloud.com/api/v2/events/{event_id}/stories" \
      -H "Authorization: Bearer $GDELT_API_KEY"
    ```

    Then `/stories/{story_id}/articles` for the source URLs. Build citation into the feed from the
    start — an alert nobody can verify gets ignored the second time it fires.
  </Step>

  <Step title="Fan out with the same id">
    This is the payoff. The `entity_id` from step 1 works unchanged on every other surface:

    <CodeGroup>
      ```python Tone over time theme={null}
      r = requests.get(
          f"https://gdeltcloud.com/api/v2/entities/{entity_id}/tone",
          headers=headers, params={"days": 30},
      )
      for row in r.json()["data"]:
          print(row["date"], row["tone"], row["article_count"])
      ```

      ```python Share of voice theme={null}
      # Share of voice REFUSES to compute without a denominator — a share with no stated
      # denominator is not a number. Pass one of query / topic / category / country /
      # region / continent / languages / source_set, and the 400 names them all if you forget.
      r = requests.get(
          "https://gdeltcloud.com/api/v2/share-of-voice",
          headers=headers,
          params={"entity_id": entity_id, "days": 30, "topic": "energy transition"},
      )
      sov = r.json()
      # The denominator comes back with a hash, so the same share is reproducible months later.
      print(sov["denominator"]["total_story_count"], sov["denominator"]["hash"])
      ```

      ```python Government awards theme={null}
      r = requests.get(
          "https://gdeltcloud.com/api/v2/gov/awards",
          headers=headers, params={"entity": entity_id},
      )
      ```

      ```python Physical assets theme={null}
      r = requests.get(
          "https://gdeltcloud.com/api/v2/facilities",
          headers=headers, params={"entity": entity_id},
      )
      ```

      ```python Ownership exposure theme={null}
      r = requests.get(
          "https://gdeltcloud.com/api/v2/exposure",
          headers=headers, params={"entity": entity_id},
      )
      ```
    </CodeGroup>

    If you had filtered by name instead, each of these would have matched a different subset and you
    would have silently dropped half the data — without any endpoint returning an error.

    <Tip>
      **Tone and share of voice are the two people find last and use most.** Tone is scored by us across
      news coverage, per entity per story per day — so it moves with what is actually being published,
      not with a sentiment model run over a headline. Share of voice puts that in context against a peer
      set: a tone drop matters differently when your coverage volume tripled the same week.

      An absent bucket means **no measured coverage**, never a neutral reading. See
      [Media Intelligence](/api-reference) in the reference for the full surface.
    </Tip>
  </Step>
</Steps>

## Doing the same thing through MCP

Same four steps, no HTTP. Point an agent at the MCP server and it discovers the tools itself:

```
1. gdelt_cloud_tool_call("search", {q: "Petrobras", universe: "all"})
2. gdelt_cloud_tool_call("events", {entity: "e_...", days: 7})
3. gdelt_cloud_tool_call("event_stories", {event_id: "..."})
4. gdelt_cloud_tool_call("gov_awards", {entity: "e_..."})
```

Use REST when you are writing a service; use MCP when a model is deciding what to call next. The
data and the gating are identical — the MCP routes to the same serve functions.

## Four things worth knowing before you build

<AccordionGroup>
  <Accordion title="Event and story lists default to 7 days" icon="clock">
    Not to all of history. Other families carry their own default, declared on each `days` parameter in the [API reference](/api-reference). Pass `days` or an explicit `date_start`/`date_end` window whenever the window matters to your answer. Windows are bounded
    — an unbounded scan across the whole corpus is refused rather than served slowly.
  </Accordion>

  <Accordion title="Read applied_filters on every response" icon="filter">
    It echoes what the server actually applied, using canonical names whatever spelling you sent. If a
    strict endpoint does not recognize a parameter, the request fails with `400 UNKNOWN_PARAM`. On an
    endpoint still using the compatibility contract, the request may succeed but list that parameter in
    `applied_filters.ignored`. In either case, correct the request before using the returned data.
  </Accordion>

  <Accordion title="null is not zero" icon="circle-half-stroke">
    A `null` metric means the observable was not found. A `0` means it was found and measured as zero.
    Charting `null` as `0` will invent a trend that is not there.
  </Accordion>

  <Accordion title="Paginate with the cursor, not an offset" icon="arrow-right">
    List endpoints return a `cursor`. Offset pagination drifts as new rows land mid-walk; the cursor does
    not.
  </Accordion>
</AccordionGroup>

## Next

<CardGroup cols={2}>
  <Card title="Recipes" icon="book-sparkles" href="/guides/recipes">
    Short copy-paste queries for the common shapes.
  </Card>

  <Card title="API reference" icon="terminal" href="/api-reference">
    Every endpoint, parameter and value.
  </Card>
</CardGroup>
