INVALID_RECORDED_INTERVAL | 400 | Recorded-time bounds or the continuation cursor are invalid or conflict with reporting dates. | Send paired recorded_start and recorded_end UTC timestamps and reuse unchanged filters with next_cursor. |
INVALID_BODY | 400 | The request body is not a valid JSON object. | Send a JSON object with Content-Type: application/json. |
UNKNOWN_FIELD | 400 | The request body contains a field the endpoint does not accept. | Use only fields documented for this endpoint and method. |
IDEMPOTENCY_CONFLICT | 409 | The Idempotency-Key was already used for a different Situation creation body. | Use the original body to recover that request, or a new key for a different request. |
IDEMPOTENCY_KEY_REQUIRED | 400 | Admitted Situation work requires an Idempotency-Key of 8–128 letters, numbers or ._:- characters. | Generate a key once and retain it across retries of the same request. |
INVALID_SEED | 400 | A Situation seed or target identifier is empty, contains whitespace/control characters, or exceeds 256 characters. | Use an identifier returned by the Story, Event or Situation API. |
SEED_REQUIRED | 400 | Neither a Story nor an Event seed was supplied for Situation creation. | Supply story_id or event_uid. |
INVALID_DIRECTION | 400 | The Situation expansion direction is not back or forward. | Use back or forward, or omit direction for forward expansion. |
SEED_MISMATCH | 409 | The selected Story does not support the supplied Event seed. | Select a Story returned by the Event’s serving links. |
AMBIGUOUS_EVENT_SEED | 409 | The Event has multiple supporting Stories, so the service cannot choose a Situation seed. | Choose one of details.stories and resend with its story_id and the Event ID. |
STORY_NOT_IN_SITUATION | 409 | The seed is not a member of the requested Situation, or the target cannot be resolved. | Use a member Story of the target Situation or omit situation_uid to discover/create the seed’s Situation. |
UNKNOWN_PARAM | 400 | A strict descriptor-backed endpoint received a query parameter it does not declare. | Read details.param, details.did_you_mean and details.accepted_params; correct or remove the parameter. |
UNSUPPORTED_FILTER | 400 | A parameter this endpoint deliberately does not support. Rejected rather than ignored. | Read details.alternatives — the reference lists a replacement for every rejected parameter. |
UNSUPPORTED_ENTITY_MATCH | 400 | An explicit entity-match policy is not implemented. The request was refused without substituting broader results. | Use entity_match=coverage or omit it to retrieve Stories mentioning or linking the entity and the Events those Stories carry. This refusal is not retryable. |
INVALID_CURSOR | 400 | The pagination cursor could not be read. Cursors are opaque — construct them only by copying pagination.next_cursor. | Restart the walk from the first page and follow pagination.next_cursor. |
CURSOR_STALE | 400 | The cursor is valid but no longer describes this result set: it was issued against a pre-built daily snapshot, and this request is either being answered live or against a snapshot that has since been rebuilt. Continuing would repeat some rows and skip others, so the request is refused rather than answered plausibly. details.reason is row_source_changed or snapshot_rebuilt. | Restart the walk from the first page. Wait for source publication to stabilize or use an earlier completed window; never force a warehouse route to bypass snapshot consistency. |
UNSUPPORTED_PARAM | 400 | A parameter that is not part of this endpoint at all. | Check the parameter reference for the endpoint you are calling. |
INVALID_MONITOR_PAYLOAD | 400 | The Monitor body failed the public Monitor schema. | details.path names the failing field. The body is strict — an unrecognised key is refused rather than ignored. |
INVALID_MONITOR_UPDATE | 400 | A Monitor PATCH was empty or named a field the update contract does not accept. | Send at least one of name, description, subject, criteria, trigger, schedule, delivery, rotate_webhook_secret, enabled. |
INVALID_MONITOR_BATCH | 400 | A batch request named no Monitors, more than 100, or an unknown action. | Send 1-100 ids and one of enable, disable, delete. |
INVALID_MONITOR_CONFIGURATION | 400 | The Monitor is syntactically valid but cannot be executed as configured. | details names the unsupported combination. |
INVALID_MONITOR_RUN_PAGINATION | 400 | limit or offset on Monitor runs was out of range or repeated. | limit 1-100, offset 0-100000, each at most once. |
INVALID_MONITOR_MATCHES_PAGINATION | 400 | limit or cursor on a Monitor run’s matched items was out of range, repeated, or not a cursor this endpoint issued. | limit 1-100, each parameter at most once. cursor is opaque and is NOT an offset — copy next_cursor from the previous page verbatim, and omit it for the first page. |
INVALID_WEBHOOK_URL | 400 | The webhook destination is not a public HTTPS URL. | Production destinations must be public HTTPS. localhost is permitted only behind the local development flag. |
INVALID_FACILITY_ID | 400 | A facility handle is not a canonical f_/s_ id. | Resolve the facility first with GET /api/v2/facilities. |
INVALID_COUNTRY | 400 | A country value is not a recognised ISO-3 code, ISO-2 code or country name. | Use ISO-3, e.g. USA. |
INVALID_ADMIN1 | 400 | An admin1 value is malformed, or was sent without exactly one country. | admin1 requires exactly one country. |
ACTOR_MATCH_NOT_SUPPORTED | 400 | subject.match = actor is not offered by Monitor v1. | Use coverage, then confirm actor involvement with the Events API after a match. |
MATERIAL_MATCH_NOT_SUPPORTED | 400 | subject.match = material is not offered by Monitor v1. | Use coverage and investigate matches with the API. |
VOLUME_SPIKE_NOT_SUPPORTED | 400 | trigger.type = volume_spike is not offered by Monitor v1. | Use new_matches. |
MONITOR_ORG_REQUIRED | 400 | Monitors are organization-scoped and the request resolved no organization. | Select an active organization. |
PREVIEW_WEBHOOK_URL_NOT_PERSISTABLE | 400 | A two-minute first-party Preview receiver URL was submitted as a saved Monitor destination. | Supply a webhook endpoint you operate. Preview receiver capabilities are temporary and cannot be persisted. |
MULTIPLE_ENTITY_HANDLES | 400 | More entity handles were sent than the parameter accepts. entity on /api/v2/events and /api/v2/stories unions a comma list up to the published cap; co_occurring_with on /api/v2/entities takes exactly one subject. | Read details.accepted_count for the ceiling and split the list across requests, merging the results. The extra handles are never silently dropped — before this code existed they were, at HTTP 200, echoed back as applied. |
OFFICE_SCOPE_TOO_LARGE | 400 | The political office sent as office= had more office-holders inside the window than one request may scope over (details.accepted_count, currently 1,000 — every national legislature we hold fits). The roster is never truncated to the first N. | Narrow the valid-time roster: send office_as_of=YYYY-MM-DD to scope to the holders on ONE date, or a shorter date_start/date_end window. details.holders is the count that was refused. |
INVALID_DATE | 400 | A date is not YYYY-MM-DD. | Use ISO dates. date_start / date_end take a calendar date only; observed_start / observed_end additionally accept a full instant such as 2026-08-01T04:04:30Z. |
INVALID_DATE_RANGE | 400 | date_start is after date_end. | Swap them. |
DATE_WINDOW_TOO_LARGE | 400 | The window exceeds this endpoint’s maximum span. | Read details.coverage and suggested_window. Use consecutive chunks of at most max_days inclusive days, not calendar months. Keep filters fixed and exhaust pagination.next_cursor in each chunk; restart without a cursor for the next chunk. |
DATE_RANGE_REQUIRED | 400 | This endpoint will not run unbounded. | Pass days or an explicit date_start/date_end. |
INVALID_LIMIT | 400 | limit is outside the accepted range. | Use the documented maximum and paginate with cursor. |
INVALID_BBOX | 400 | A bounding box is malformed or inverted. | The axis order differs by family, and a swapped box usually returns an empty or wrong 200 rather than this error. Events, stories, facilities and energy take latitude first — lat_min,lon_min,lat_max,lon_max. Maritime takes longitude first — min_lon,min_lat,max_lon,max_lat. The per-parameter description in the spec is authoritative for the path you are calling. |
INVALID_NEAR | 400 | The near grammar does not match this endpoint’s. | Events and stories take lat,lon with a separate radius_km; facilities and energy take a single lat,lon,radius_km triple. The spec carries the right example per path. |
INVALID_ENTITY_ID | 400 | A bare name was sent where a resolved id is required. | Resolve it first with GET /api/v2/search, then pass the e_… id. |
AMBIGUOUS_LEGACY_ID | 409 | A legacy facility-unit id maps to more than one canonical physical site. | Use a canonical s_… site id returned by GET /api/v2/facilities; candidate ids are in details.canonical_site_ids. |
ENTITY_REQUIRED | 400 | This endpoint is per-entity and no entity was named. | Pass entity. |
FILTER_REQUIRED | 400 | This endpoint needs at least one narrowing filter. | Add a geography, taxonomy or entity filter. |
INVALID_LIST_SOURCE | 400 | An unknown screening list, or one we declare but do not yet carry. | Branch on details.reason: unknown_source_key vs declared_but_not_ingested. Never an empty success — on a sanctions surface that would read as a clean result. |
EVENT_FAMILY_CATEGORY_CONFLICT | 400 | A deprecated event_family contradicts the category sent with it. | Drop event_family; category implies the family. |
INCOMPATIBLE_PARAMETERS | 400 | Individually valid parameters describe a combination this endpoint cannot answer honestly. | Read details.required and remove or replace the conflicting values. |
SUBCATEGORY_REQUIRES_CATEGORY | 400 | subcategory was sent with no category. Sub-event types are scoped by category and several labels are ambiguous without one. | Send category too. details.accepted_categories lists the categories that have subcategories. |
INVALID_SUBCATEGORY_FOR_CATEGORY | 400 | The subcategory is a real taxonomy code but not one that belongs to the category sent with it. | Read details.accepted_values — it is the subcategory list for the category you actually asked for. |
STORY_CATEGORY_CONFLICT | 400 | story_category and the deprecated category spelling of it were both sent, naming different values. | Send one. story_category is the current spelling. |
INVALID_NUMBER_RANGE | 400 | A metric _min is greater than its _max, so the range is empty. Refused rather than served as a (correct but useless) empty result. | Read details.min_value / details.max_value and swap them. |
EVENT_FAMILY_METRIC_CONFLICT | 400 | A metric only one event family publishes (magnitude, systemic_importance, propagation_potential, market_sensitivity — all CAMEO+) was combined with a taxonomy filter that commits the request to the other family. Unsatisfiable by construction: the conflict detail table has no such column. | Read details.metric_family and details.implied_family — they disagree. Drop the metric filter, move the taxonomy filter into details.metric_family, or use a metric both families publish (details.accepted_metrics_for_family). |
INVALID_NUMBER | 400 | A numeric parameter is not a number, or is outside the range this endpoint publishes for it. Also raised for days / window when the relative span is not a whole number of days inside the endpoint’s cap. | The message names the bound that was broken, and it is the same bound the OpenAPI schema publishes for that parameter (minimum / maximum). Where the registry validates the parameter the body carries details.param, details.invalid_value and those bounds; the days / window form carries details.max_days. |
INVALID_BOOLEAN | 400 | A boolean parameter was sent something that is not a boolean literal. Refused rather than read as false, because a filter silently dropped to its negative case answers a different question at HTTP 200. | Send true or false; case is not significant, so TRUE is accepted. 1 / 0 and yes / no are not — the vocabulary is exactly the two words. |
CONFLICTING_TIME_WINDOW | 400 | An explicit window (date_start / date_end) and a relative one (days or window) were both sent. Only one can apply, and silently choosing either would return rows outside the range you asked for. | Send one of them; details.params lists what arrived. date + days is deliberately still allowed — that composes into a relative span ending on a fixed date rather than contradicting it. |
INVALID_SITUATION_ID | 400 | The Situation address is not a valid Story ID or Situation UID. | Copy an ID from the Stories or Situations endpoint; do not pass a title or URL as the path ID. |
INVALID_PARAM | 400 | A candidate-search parameter has an invalid value or is incompatible with the selected source universe or entity type. | Use the accepted values in details when present. Politicians require people with office evidence; facility candidates use all or reference sources. |
INVALID_SCOPE_VERSION | 400 | A Situation page continuation supplied a malformed scope fingerprint. | Use the scope_version returned by the preceding page, or restart at the first page without a fingerprint. |
INVALID_WINDOW | 400 | The date_start / date_end span on a Situation is inverted, longer than this endpoint will walk, or — on /api/v2/situations — a country, category or entity facet was sent without BOTH reporting bounds. The dates that are present are well-formed; the WINDOW is what is refused. | Order the dates and keep the span within details.max_days (30). With a country, category or entity facet, always send both bounds. On the detail route, sending neither is usually right: the window defaults to the anchor Story’s own date rather than to today. |
INVALID_SEARCH | 400 | The free-text search term was refused before any query ran: it is too short to narrow anything (/api/v2/stories requires two characters), or it names more terms than the endpoint will conjoin (/api/v2/situations matches at most details.max_terms, since every term must appear in one stored title). | Read details — it names the parameter and, where a count applies, term_count against max_terms. Shorten the phrase rather than expecting the extra terms to be dropped; nothing is truncated silently. To search what a Situation is ABOUT rather than what its headline says, use GET /api/v2/stories?search= and take a returned Story id to /api/v2/situations/{story_id}. |
INVALID_SUBCATEGORY | 400 | A subcategory value is not a sub-event type this API carries at all. Distinct from INVALID_SUBCATEGORY_FOR_CATEGORY, where the code is real and simply belongs to a different category. | Read details.accepted_values — it is already scoped to the categories you sent — and details.nearest_values for the closest spellings to what you wrote. |
MULTIPLE_STORY_CATEGORIES | 400 | More than one Story-cluster category arrived through the deprecated category spelling. A Story carries exactly one. | Send a single story_category, and keep category for the linked Event categories. details.accepted_values lists the Story vocabulary. |
CONFLICTING_FAMILY_FILTER | 400 | domain=CONFLICT names the conflict family — it is the value an event card prints in its domain field — and an event_family naming a different family was sent alongside it. Both cannot apply. | Send event_family=conflict on its own, or a CAMEO+ domain together with event_family=cameoplus. Sent alone, domain=CONFLICT is translated to the family filter that means it. |
INVALID_MONITOR_QUERY | 400 | A query Monitor contains unsupported or competing parameters, an unresolved identity, or fixed dates/pagination in its standing parameters. | Use an allowlisted endpoint and its documented parameters, resolve selected IDs with /api/v2/search, and keep discovery dates in source_request. Scheduled matching uses committed records since the previous successful checkpoint. |
MONITOR_QUERY_RETRIEVAL_BOUNDED | 422 | Semantic relevance retrieval is a bounded candidate pool and cannot establish exhaustive scheduled query matching. | Explicitly choose search_mode=lexical for literal phrase matching over served text, or remove the text query and use supported structured filters. This changes matching meaning and is never automatic. |