> ## 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.

# Situations

> Follow a maintained collection of Stories and coded Events about one occurrence or developing thread, inspect its evidence, and use the same result through the API.

A **Situation** brings together the Stories and coded Events that describe one real-world
occurrence and its developments: what materially led to it, reporting about the occurrence,
and what followed. A flood and its aftermath or the stages of a particular trade dispute can
form a Situation. Sharing a country, company or topic is not sufficient for membership.

Use Situations when your application needs the developing thread around an occurrence, rather
than another page of headlines. Start with the maintained collection, inspect the member
Stories and incidents, follow the evidence, and save the response your application used.
The [Situations workspace](https://gdeltcloud.com/situations) demonstrates the same read API.

## Stories, Events and Situations

| Object    | What it tells you                                                                               |
| --------- | ----------------------------------------------------------------------------------------------- |
| Story     | Related article coverage, with source articles and linked entities.                             |
| Event     | A coded action or development, with classification, actors, occurrence date and geography.      |
| Situation | A maintained collection of Stories and their Events around one occurrence or developing thread. |

Situation membership is adjudicated automatically from evidence. **Curated means a stored,
adjudicated collection; it does not mean a human reviewed every member.** Membership can be
incomplete or mistaken. A Story can belong to more than one Situation when it contributes to
more than one developing thread.

## Find a maintained Situation

Use your existing [API key](/api-keys):

```bash theme={null}
curl --get 'https://gdeltcloud.com/api/v2/situations' \
  --header "Authorization: Bearer $GDELT_API_KEY" \
  --data-urlencode 'limit=10'
```

Each list row provides a `situation_uid`, title, span, origin and peak pointers, and full serving
totals. Take a returned `situation_uid` to the detail endpoint. The UID is the identity;
the title and representative Stories can change as the collection develops. A merged
Situation's old identifier resolves forward to its survivor.

List dates select Situations whose current serving span **overlaps** the requested window.
`totals` and `scope` describe the whole Situation; `selected_scope` describes member reporting
in your requested dates. Selection, ordering and counts use the same current serving membership.
`counts_updated_at` is serving freshness, while `scope.version` identifies the measured content.
Article totals sum member Story counts; they do not assert globally unique article URLs.

The list's `search` matches words in the stored title. It does not search every member or
article, and it is not semantic search. For broader discovery, search
[`/api/v2/stories`](/api-reference) and pass a returned Story ID to the Situation detail endpoint.
That route may return an exploratory result, as explained below. Consult the
[parameter reference](/reference/parameters) for supported filters rather than assuming every
Event or Story filter also applies to Situations.

## Inspect the collection and its evidence

Replace `SITUATION_ID` with a UID returned by the list:

```bash theme={null}
curl --get "https://gdeltcloud.com/api/v2/situations/$SITUATION_ID" \
  --header "Authorization: Bearer $GDELT_API_KEY" \
  --data-urlencode 'limit=25' \
  --data-urlencode 'include=edges'
```

The detail response includes member `stories`, coded `incidents`, an entity cast, daily
rollups, composition and totals. Follow each member's `story_id` to
`/api/v2/stories/{story_id}` and `/api/v2/stories/{story_id}/articles` to inspect its evidence.
Use the returned Event identifiers for the fuller Event records, including date and casualty
evidence where available.

`origin` points to the earliest member with material coverage, and `peak` to the largest
member by article coverage. Neither is a claim about the exact beginning of the real-world
occurrence, and the peak is not necessarily its most severe moment. Read the coded Events
for occurrence dates; Story dates describe the coverage being assembled.

## Page Stories, Events and Entities independently

Use `/api/v2/situations/{story_id}/stories`, `/events`, and `/entities` for complete
membership discovery. The published path token is `story_id` on every Situation route; the member
lists accept only a stored `sit_…` UID. Each defaults to 25 records; its own `pagination.total` and `has_more`
are independent of graph limits. Send the same reporting dates on every page. Keep the first
response's `scope.version` and send it as `scope_version` with later offsets. If the evidence
changes, HTTP 409 asks you to restart instead of silently combining different editions.
Entity versions also reflect canonical identity resolution and linked coverage. The workspace keeps each evidence page and its version when you change tabs or reopen its link; an explicit restart begins with the current membership.

<CodeGroup>
  ```bash Situation Paging theme={null}
  curl --get "https://gdeltcloud.com/api/v2/situations/$SITUATION_ID/entities" \
    --header "Authorization: Bearer $GDELT_API_KEY" \
    --data-urlencode 'limit=25' \
    --data-urlencode 'offset=25' \
    --data-urlencode "scope_version=$SCOPE_VERSION"
  ```

  ```python Situation Paging theme={null}
  import os
  import requests

  BASE = "https://gdeltcloud.com/api/v2"
  HEADERS = {"Authorization": f"Bearer {os.environ.get('GDELT_API_KEY', '')}"}
  MAX_RESTARTS = 3


  def fetch_page(situation_uid, offset, scope_version=None, limit=25, session=requests):
      params = {"limit": limit, "offset": offset}
      if scope_version:
          params["scope_version"] = scope_version
      return session.get(f"{BASE}/situations/{situation_uid}/entities", headers=HEADERS, params=params)


  def walk_entities(situation_uid, limit=25, session=requests):
      """Every linked entity, restarting from page 0 whenever the membership scope changes."""
      offset, scope_version, restarts, collected = 0, None, 0, []
      while True:
          response = fetch_page(situation_uid, offset, scope_version, limit, session)
          if response.status_code == 409:
              # The scope changed between pages (code SITUATION_SCOPE_CHANGED). Mixing editions would
              # silently combine two memberships, so discard the partial walk, drop the version and
              # start again at offset 0.
              restarts += 1
              if restarts > MAX_RESTARTS:
                  raise RuntimeError("membership kept changing; try again later")
              offset, scope_version, collected = 0, None, []
              continue
          response.raise_for_status()
          body = response.json()
          scope_version = body["scope"]["version"]
          collected.extend(body["data"])
          if not body["pagination"]["has_more"]:
              return collected
          offset += limit


  if __name__ == "__main__":
      for entity in walk_entities("sit_e64ab6cc5c964749aee1"):
          print(entity["entity_id"], entity["name"])
  ```
</CodeGroup>

In the workspace, the timeline day control focuses the map and Overview evidence. It does not change the reporting dates used by the paginated tabs: an Event can occur on a different date from the reporting that links it. Use **Reporting from / Through** to change those API filters.

Detail `scopes.whole` and `scopes.selected` make whole-Situation versus window totals explicit.
The graph, its cast and incident arrays remain bounded; `caps` says what was returned. The
adjudication split describes the returned incident rows. Membership route counts are not an
independently confirmed count of real-world incidents.

## Dated background and changes

When available, `narrative` contains a short background and changes in reporting, each with
supporting Story IDs. `method=extractive` labels attributed verbatim headline excerpts used when a synthesis cannot be supported. A separate adversarial check must approve the claims against served
reporting before publication. The response names its evidence sample size, full member count,
publication date and content version. These summaries are reporting summaries, not independent
verification that every reported claim is true.

Failed regeneration retains the previous dated summary. `narrative.current=false` means newer
served evidence exists; `narrative=null` means no verified summary is currently available.
A snapshot preserves its full member manifest and measured scope. The summary covers the whole
Situation even when the evidence tabs use narrower dates.

Maintained Situations advance and discover qualifying threads hourly. Reconciliation and merging
run overnight, preserving the existing daily creation budget. The two-day Story-link candidate
window limits link discovery, not how long a Situation can continue. Published freshness and
partial/error maintenance runs remain distinct from a guarantee of exhaustive coverage.

## Stored membership and exploratory results

The public discovery page initially shows Situations with **at least 2 member Stories and
2 coded Events** across their whole current servable membership. These are combined requirements;
narrowing the reporting dates does not reapply them to just that slice. **Include emerging
Situations** lowers the request to `min_stories=1&min_events=0`. The REST API applies these floors
only when supplied: existing calls that omit them still discover all nonempty, unmerged Situations.
The page's default request is `GET /api/v2/situations?min_stories=2&min_events=2`.

These filters determine what discovery promotes; they do not delete seeds or prevent direct access
and growth. Automatic creation examines the reporting day's top 400 Stories by their own article
count, requires a cross-day `precursor` or `continuation` link, then ranks candidates by the seed's
article count plus its linked neighbors' article counts. That combined coverage must reach 250
articles; at most 50 automatic Situations can be created per UTC day. There is no coded-Event
minimum at creation. The initial membership contains only the seed Story. A separate membership
judge decides which other Stories belong, so a large candidate thread can still produce a small
maintained collection if its proposed members are rejected. User-requested creation starts from
a served Story or a supporting Story for an Event and does not use the automatic coverage floor.

The scheduled workflow advances and seeds at minute 20 each hour. At 05:20 UTC it also reconciles
the history and nearby dates, then considers merges. A newly created seed may therefore remain
small while it awaits expansion; no growth is guaranteed.

Always read `meta.situation_source`:

| Value     | How to use the result                                                                                                                                               |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `curated` | Stored membership in a maintained Situation, with a `situation_uid`. The list discovers these collections.                                                          |
| `walked`  | An exploratory neighbourhood around a Story that has no stored Situation. Related pairs were adjudicated, but the resulting graph is not an adjudicated collection. |

A Story ID resolves to its primary stored Situation where one exists. Otherwise the detail
API can walk related Stories. At greater `depth`, a member may be reached through another
Story without having been compared directly with the anchor. Inspect `hop`, `via_story_id`
and the optional `edges`; reachability is a weaker claim than Situation membership. On a
stored Situation, `depth` is inapplicable and is reported under `applied_filters.ignored`.

The maintained workspace focuses on stored Situations. Browsing or exporting an exploratory
API response does not create a stored Situation. Building or expanding one is separate from
the read API described here.

## What a relationship means

Two different fields answer two different questions:

* **`stories[].route`** labels the member's place in the thread: coverage of the occurrence,
  an antecedent, a consequence, or another telling of the same incident. An expansion can
  assign antecedent or consequence from the Story's date relative to the collection's bounds.
  The label alone does not mean a judge established a causal relationship.
* **`stories[].relation` and `edges[].relation`** describe chronology or duplicate identity.
  A precursor occurs earlier; a continuation occurs later. Chronological order alone does not
  show that one development caused the other.

An edge's `edge_source` describes how a candidate pair was proposed. Shared entities or
embedding similarity can help find candidates; neither establishes membership on its own.
Confidence describes the adjudicator's judgment, not a calibrated probability of causation.

Read each Story's `membership` alongside the label. Its `basis` distinguishes a seed,
stored membership and a pairwise path; `route_basis` explains how the route was assigned.
`reason` carries a recorded membership explanation when available. `evidence_status` tells
you whether that decision is available, was not recorded, was not loaded, or whether only
pairwise evidence exists. A null reason is not a positive finding or a synthesized explanation.
Where present, `decision` identifies the verdict, policy version and decision time, and `via`
identifies the Story or Situation through which the membership was reached.

Same-incident Stories are represented differently on the two read paths. Stored Situations
retain them as members with route `same_incident`; exploratory results fold duplicates out
and report them under `duplicates`. Use the returned distinct counts and adjudication split
rather than treating every Story or Event row as a different real-world incident.

## Follow connected entities and offices

The entity cast identifies entities linked to member Stories. Its `story_ids` let you trace
the connection back to those Stories. **Appearance in the cast is not a claim that the entity
acted in, caused, or was affected by every Event.** Use Event actor roles and source evidence
to establish those stronger relationships.

Reuse the returned entity ID for its profile and available source sections. A person's
[political offices](/data/political-offices) provide dated actor context. Office-held facts,
public-list entries, ownership relationships and news co-occurrence answer different
questions; preserve their source and relationship meaning when combining them.

Source sections follow their existing entitlements. An unavailable or withheld section is
not evidence that the entity has no such records. In particular, offices use published
holding dates for valid-time `as_of`; that is different from a knowledge-time snapshot of
what the service knew then.

## Limits, uncertainty and export

`limit` and `offset` page the member Stories. Incidents, the entity cast, daily rollups and
totals describe the admitted collection, so they can refer to Story IDs outside the current
page. For a stored Situation, `max_nodes` limits the graph sample and never changes full-scope totals.
For an exploratory walked result it bounds the discovered neighbourhood. Inspect
`caps.members`, `caps.incidents`, `caps.entities` and `truncated` before treating an array as
complete. A missing optional `edges` block means it was not requested, not that no links exist.

A member's `facts_status` distinguishes available Story facts from an unavailable hydration.
An unknown `linked_event_count` is null; it is not a measured zero Events.

Fatality fields are reported figures: neither the maximum nor the raw sum is a verified death
toll for the entire Situation. Events may overlap or have unadjudicated identities. Unknown
values remain `null`; do not turn them into zero or sum overlapping reports into a new fact.

Save the complete API response, including metadata, filters, caps and timestamps:

```bash theme={null}
curl --get "https://gdeltcloud.com/api/v2/situations/$SITUATION_ID" \
  --header "Authorization: Bearer $GDELT_API_KEY" \
  --data-urlencode 'limit=25' \
  --data-urlencode 'include=edges' \
  --output situation-response.json
```

The workspace's **Download timeline JSON** saves the current timeline response. The linked-entity browser has a separate **Download entity page** action. Each saves a bounded response,
not the complete dataset or an automatic retrieval of remaining pages. It does not require
the separate [bulk data download](/data/bulk-events-schema) entitlement. Changing parameters
or fetching another page makes another API request; downloading an already rendered view
does not run adjudication or refresh the collection.

Timeline search, entity, day, coded-Event and sort controls are local filters. They do not
change the downloaded API response. **Copy window link** preserves the requested reporting
window, not those local controls; **Download timeline JSON** saves the bounded response before
those local filters.

A saved response records what you received. A later read can contain new members, revised
counts or merged identities; the current service does not reconstruct every earlier version
of a Situation on demand. Keep the response locally if your workflow needs that receipt.

## Access

Situations reads use existing API authentication and Query Unit allowances. There is no
separate Situations subscription or premium feature flag. Explore evaluation, the 50 QU/month web
allowance after an evaluation ends — API reads need an active evaluation or a paid plan
(`403 PROGRAMMATIC_ACCESS_DENIED` otherwise, see [errors](/reference/errors)) — paid plans and
legacy overrides follow the same access rules as other API reads. Consult your account's current
allowance and the [pricing page](https://gdeltcloud.com/pricing) for limits; individual connected
data sources retain their own access rules.

## Browse all linked entities

`GET /api/v2/situations/{story_id}/entities` accepts a stored `sit_…` UID and pages
canonical entities across its **full current servable membership**. This is independent
of the detail response's bounded member view and top entity cast.

```http theme={null}
GET /api/v2/situations/sit_e64ab6cc5c964749aee1/entities?limit=20&offset=40
```

Use `summary.entity_count` for the distinct canonical count and `summary.by_type` for
available type filters. `entity_type=person` filters before pagination. `pagination.total`
counts the type-filtered set; the summary always describes the whole requested scope.
Optional `date_start` and `date_end` scope membership by reporting date. Omit both to
inspect the full membership. An empty measured set returns zero; a failed or over-budget
measurement returns an error, never a partial exact total.

Each row includes canonical `entity_id`, name, type, distinct `story_count`, coverage counts,
first/last reporting dates, source entity IDs and identity basis. Story breadth is deduplicated
after identity folding. Article and mention counts sum resolved-link coverage and can overlap
across aliases. Linked coverage does not establish an actor role, ownership or exposure.

## Filter discovery by linked Event evidence

```http theme={null}
GET /api/v2/situations?date_start=2026-08-26&date_end=2026-09-04&country=NPL&category=ENVIRONMENT
```

`country` means **coded Event location**, using ISO-3 codes; actor nationality is not used.
`category` uses the Event taxonomy, not Story category. A Situation qualifies when **any
member-linked Event matches both filters**. Neither field claims a primary Situation country
or category. Both reporting date bounds are required when using either filter, with a maximum
of 30 inclusive days. Filtering happens before pagination and its exact match total.

Directory `totals.entity_count` is separately measured over each returned Situation's full
current servable membership. `entity_count_status=unavailable` and a null count mean that
measurement failed. Existing Story/article/Event totals retain their stored-span scope and
`counts_updated_at`; they are not recomputed to the discovery filter window.

## Refine discovery and inspect connections

Use `entity`, `story_id` or `event_uid` to find maintained memberships. Entity, country and category
filters require a reporting range of at most 30 days. The default `sort=recent` uses reporting dates
and genuine first-observed evidence; maintenance and settle refreshes do not promote a Situation.
`category_summaries` and `selected_scope` describe the selected reporting window, while `totals`
retains whole membership. Sharing a country, role or name does not establish membership.

`GET /api/v2/situations/{story_id}/connections` pages the complete membership list with relationship,
reporting date, first-observed timestamp and available adjudication provenance. Stories, Events,
Entities and Connections have independent limits, offsets and `scope_version` witnesses. Keep each
witness and the same dates while paging; restart that list from offset zero after a scope-change
409\. Graph samples never limit list totals. Entity roles require explicit Event actor bindings;
reporting mentions alone do not establish participation.

## Create a shared Situation

Paid subscribers and active trials can call `POST /api/v2/situations` with `story_id` or `event_uid`.
An Event with several supporting Stories returns `409 AMBIGUOUS_EVENT_SEED` and candidate Stories;
choose one explicitly. Reuse an existing canonical Situation for 0 QU. Admitted creation or
expansion costs 5 QU, with a quota reservation and refund on failed work.

Send a stable `Idempotency-Key` header of 8–128 letters, digits or `._:-` characters. Store that key
before sending and reuse the same key and JSON body after a timeout. A repeated completed request
returns its original receipt without another charge. A running request returns a retryable
in-progress response; changing the request under the same key is refused. Optional `situation_uid`
selects a Situation to expand, and its seed Story must already belong to that Situation.

The MCP `create_situation` tool runs through `gdelt_cloud_tool_write`; its API reservation owns
billing, so the MCP wrapper does not add another 1 QU. Show the 5 QU price before creating.

A complete request, with the two rules that keep a retry from double-charging: **the same body
after a timeout reuses the SAME key; a different body needs a NEW key.** The Story id below is the
one the [quickstart](/quickstart) response returned; any served Story id works.

<CodeGroup>
  ```bash Situation Create theme={null}
  export IDEMPOTENCY_KEY="sit-create-$(date +%s)-nepal-flood"   # store it before sending
  curl -X POST 'https://gdeltcloud.com/api/v2/situations' \
    --header "Authorization: Bearer $GDELT_API_KEY" \
    --header "Idempotency-Key: $IDEMPOTENCY_KEY" \
    --header 'Content-Type: application/json' \
    --data @body.json
  ```

  ```json Situation Create Body theme={null}
  { "story_id": "743d7e77540a" }
  ```

  ```json Situation Create 409 theme={null}
  {
    "success": false,
    "error": "Choose a supporting Story to seed this Situation.",
    "code": "AMBIGUOUS_EVENT_SEED",
    "details": {
      "stories": [
        { "story_id": "743d7e77540a", "story_date": "2026-08-26" },
        { "story_id": "26826d1cf2e2", "story_date": "2026-08-26" }
      ],
      "truncated": false
    }
  }
  ```

  ```python Situation Create theme={null}
  import os
  import uuid
  import requests

  BASE = "https://gdeltcloud.com/api/v2"
  HEADERS = {"Authorization": f"Bearer {os.environ['GDELT_API_KEY']}"}


  def create_situation(body, idempotency_key):
      """One admitted creation per key. Retry a timeout with the SAME key and body; a new body needs a NEW key."""
      return requests.post(
          f"{BASE}/situations",
          headers={**HEADERS, "Idempotency-Key": idempotency_key, "Content-Type": "application/json"},
          json=body,
          timeout=60,
      )


  key = f"sit-create-{uuid.uuid4()}"  # store the key before sending, so a timeout can be retried with it
  response = create_situation({"story_id": "743d7e77540a"}, key)
  if response.status_code == 409 and response.json().get("code") == "AMBIGUOUS_EVENT_SEED":
      # An Event seed with several supporting Stories. Choose one explicitly — and because that is a
      # DIFFERENT body, send it under a NEW Idempotency-Key, never under `key`.
      for candidate in response.json()["details"]["stories"]:
          print(candidate["story_id"], candidate["story_date"])
      raise SystemExit("Choose one supporting Story and resend it as story_id with a new Idempotency-Key.")
  print(response.status_code, response.json())
  ```
</CodeGroup>

The 409 body above is the exact shape the service returns: `details.stories` lists up to 100
candidate Stories with their reporting dates, and `details.truncated` says whether more exist.
A 409 `IDEMPOTENCY_CONFLICT` means the key was already used for a different body; a 409
`CREATION_IN_PROGRESS` means the same request is still running — retry it with the same key.
Every 503 on this route releases the quota reservation and names its retry in `error`.

## Coverage, measured

A read-only `GET /api/v2/situations?limit=1` against the production serving tables on
2026-09-07 (23:55 UTC) reported `pagination.total` of **18 maintained Situations**, and
**16** of them with at least one coded Event (`min_events=1`). That is the whole stored
collection at that moment — a small, deliberately bounded set, not a sample of a larger one.
Read the number with its counterexamples:

* Membership is adjudicated automatically and can be incomplete or mistaken; `curated` means
  a stored, adjudicated collection, not a human-reviewed one.
* The automatic seed cap is 50 new Situations per UTC day, so a day with more qualifying
  threads than that leaves some unopened until the next day.
* Seven days without a qualifying development makes a Situation inactive; the total counts
  what is maintained, not everything that ever opened.
* A Story can belong to more than one Situation, so member totals across Situations overlap
  and do not sum to a count of distinct Stories.
* Exploratory `walked` results returned for a Story with no stored Situation are not in this
  total at all.

The number will move; the shape of the caveats will not. Re-measure before quoting it.

## Public daily editions and maintenance

Public `/situations/{uid}` pages display an immutable edition published through the last completed
UTC day. Cutoff and publication time are shown together. Evidence, counts, narrative and all
paginated lists are frozen together; a failed or incomplete publication retains the previous
edition. Signed-in `/view/situations/{uid}` pages use live serving data. Public page filter variants
are not indexed, and merged IDs redirect to their canonical identity.

Active Situations advance hourly at minute 20; reconciliation and merging run at 05:20 UTC.
Seven days without a qualifying development makes a Situation inactive. Routine refreshes do not
reset that clock. Bounded archived retrieval can propose new evidence, but independent membership
adjudication is required to reactivate the same UID. The automatic seed cap remains 50 per UTC day;
paid creation is accounted separately. Maintenance records model/service tier, duration, attempts,
tokens and estimated cost using versioned rates; unrecorded historical usage stays unavailable.
