Build Your First Integration

From zero knowledge to live humanitarian data in about ten minutes — the mental model, your first authenticated request, and two real queries.

This is the front door. If you’ve never touched the CLEAR API before, read this page top to bottom. By the end you’ll understand how the data is shaped and you’ll have pulled real records with your own API key. Everything else in these docs — the Queries, Mutations, and Types reference — is there to look things up after you understand the shape of the system.

1. The mental model

CLEAR is a five-tier graph. Raw observations flow in at the bottom and are progressively grouped, classified, and escalated into human-readable advisories. Almost every query you write touches one of these five tiers:

TierWhat it represents
LocationThe administrative hierarchy — country → state → district → point. Everything else hangs off this tree.
SignalA single raw observation ingested from a source (Dataminr, ACLED, GDACS) or filed manually by a field officer.
EventA cluster of related signals, classified by disaster type and bound to a district.
AlertA published advisory raised from a severe event and delivered to subscribed users.
CrisisA user-curated aggregation of related events, enriched by an LLM with a summary, forward scenarios, and a needs analysis.

Two things to internalise. First, the tiers build upward: a Signal links to the Event it belongs to, which links to any Alert raised from it. Second, everything is geolocated — signals, events, and alerts all reference the Location hierarchy. That’s why the “by location” queries are the most powerful way to slice the data.

One staging area sits in front of tier 2. The ground staging tier holds staged Signals — messages ingested from ground sources such as WhatsApp groups (GroundMessage) — grouped into threads (GroundThread). A thread is a cluster of staged Signals with a correction lifecycle; when a reviewer approves a thread for publication, it is promoted and becomes a Signal. Nothing enters the signals graph from a ground source without passing that review gate.

New to GraphQL? You send one POST request to /graphql with a query naming exactly the fields you want, and you get back exactly those fields — no over-fetching, no guessing at response shapes. The GraphQL Sandbox autocompletes every field as you type, which is the fastest way to explore.

2. Get set up

You need an account and an API key. Both live in the Developer Portal.

1

Create an account

Sign up at the Developer Portal. It takes about ten seconds.

2

Generate an API key

In the portal, open API Keys and create one. Copy it immediately — the full key is shown only once.

Treat your key like a password. Keep it in an environment variable or secrets manager — never in client-side code or version control.

3. Your first request

The me query is the simplest authenticated call: it returns the account your key belongs to. Run it first to confirm your key works before you touch real data. Replace YOUR_API_KEY with the key you just generated:

curl -X POST https://api.clearinitiative.io/graphql \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"{ me { id email role } }"}'

A successful response echoes your own user back:

{
  "data": {
    "me": {
      "id": "aBc123XyZ",
      "email": "you@example.com",
      "role": "viewer"
    }
  }
}

If me comes back null, your key isn’t being read — double-check the Authorization header and that the key hasn’t been revoked in the portal.

4. Pull real data

Now something useful. Locations are the backbone of the graph, so start there: this lists every country CLEAR tracks (hierarchy level 0), with each one’s id, name, and resident population.

curl -X POST https://api.clearinitiative.io/graphql \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"{ locations(level: 0) { id name population } }"}'

You’ll get back an array of Location objects. Grab the id of a country that interests you — you’ll feed it into the next query.

level walks the hierarchy: 0 = country, 1 = state/province, 2 = city, and so on. Pass a country’s id to location(id: …) and request children to drill down a level at a time.

5. Slice by location

This is where it gets powerful. eventsByLocation returns every event whose origin, destination, or general location falls within a location and all of its descendants — so one country id gives you everything happening anywhere inside that country. Drop in the id from the previous step:

curl -X POST https://api.clearinitiative.io/graphql \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"query($loc: String!) { eventsByLocation(locationId: $loc) { id title severity types generalLocation { name level } } }","variables":{"loc":"YOUR_LOCATION_ID"}}'

Each Event comes back with a severity from 1 (low) to 5 (severe), its disaster-type types tags, and the place it’s bound to. From any event you can follow the graph further — ask for its signals to see the raw observations underneath, or its alerts to see what was escalated from it.

The plain events, signals, and alerts lists are scoped to a team. If you’re not an admin, pass a teamId for a team you belong to (find yours via the myTeams query). The …ByLocation queries shown here have no such requirement, which is why they’re the easiest place to start.

Where to go next

You now know the shape of the graph and how to authenticate, query, and slice by location. From here:

  • Browse the full Queries and Types reference below — it’s auto-generated from the live schema, so it’s always current.
  • Open the GraphQL Sandbox to build queries interactively with field autocomplete.
  • Read the Authentication section for the browser/cookie flow if you’re building a web app rather than a server integration.
  • Manage your keys anytime in the Developer Portal.

Introduction

Welcome to the CLEAR API - your gateway to humanitarian intelligence.

The CLEAR API gives you programmatic access to signals, events, alerts, data sources, and geographic location data through a single GraphQL endpoint. Whether you’re building a monitoring dashboard, integrating alerts into your workflow, or analysing humanitarian patterns, this API has you covered.

Everything here is accessible via GraphQL at /graphql. You send a query describing exactly the data you want, and you get back precisely that — nothing more, nothing less.

What You Can Do

FeatureDescription
SignalsAccess raw data items collected from data sources, with location links and metadata.
EventsBrowse grouped signals forming coherent narratives, with location, population, and type data.
AlertsView events escalated for notification, delivered to subscribed users.
Data SourcesDiscover the external data feeds (ACLED, FEWS NET, social media monitors) that supply signals.
LocationsQuery a hierarchical geographic tree — countries, states, cities — with PostGIS geometry.
Disaster TypesLook up disaster classifications with GLIDE numbers.
Feature FlagsCheck runtime feature toggles to adapt your application’s behaviour.
API KeysCreate and manage personal API keys for server-to-server authentication.

Quick Start

Go from zero to your first API response in three steps.

1

Create an account

Head to the Developer Portal and sign up. It takes about ten seconds.

2

Generate an API key

In the portal, go to API Keys and create one. Copy it immediately — you won’t see it again.

3

Make your first query

Send a request with your key in the Authorization header:

curl -X POST https://api.clearinitiative.io/graphql \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"{ me { id email } }"}'

Want to explore interactively? Open the GraphQL Sandbox to browse the full schema, autocomplete queries, and test requests in your browser.

Authentication

Two ways to authenticate, depending on your use case.

API Keys (server-to-server)

Pass your key as a Bearer token in the Authorization header:

Authorization: Bearer sk_live_your_key_here
Never expose API keys in client-side code or version control. Store them in environment variables or a secrets manager.

Session Cookies (browser apps)

Sign in via the REST auth API. The session cookie is set automatically and sent with subsequent requests:

// Sign in
const res = await fetch('/api/auth/sign-in/email', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',
  body: JSON.stringify({ email: 'you@example.com', password: '...' }),
});

// Then query (cookie sent automatically)
const data = await fetch('/graphql', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',
  body: JSON.stringify({ query: '{ me { id email } }' }),
});

Queries

NameReturnsDescription
me User Returns the currently authenticated user, or null if not signed in.
users [User!]! List all users.
user User Look up a user by ID.
id: String!
alerts [Alert!]! List alerts. Requires authentication. Admins may omit teamId to list all; non-admins must provide a teamId for a team they belong to.
status: AlertStatusteamId: StringincludeDummy: Boolean
alert Alert Look up an alert by ID. Requires authentication. Non-admins can only access alerts within their team scope.
id: String!
signals [Signal!]! List signals. Requires authentication. includeDummy defaults to false.
teamId: StringincludeDummy: Boolean
signal Signal Look up a signal by ID. Requires authentication. Non-admins can only access signals within their team scope.
id: String!
signalsByLocation [Signal!]! List signals by location. Returns all signals whose origin, destination, or general location is within the given location (including descendants).
locationId: String!
signalLocationChallenges [SignalLocationChallenge!]! Open Signal Location challenges (for map dual-pin rendering). Team-scoped like signals when teamId is given; status defaults to "consideration".
teamId: Stringstatus: String
pendingSignals [Signal!]! Signals awaiting downstream processing (status = NEW), oldest-first — the Dagster event-driven drain. `source` filters by DataSource name (e.g. "dataminr"); `first` caps the batch (default 100, max 500). Admin/pipeline only.
first: Intsource: String
events [Event!]! List events. Requires authentication. includeDummy defaults to false.
teamId: StringincludeDummy: Boolean
event Event Look up an event by ID. Requires authentication. Non-admins can only access events within their team scope.
id: String!
publicEvent PublicEvent Resolve a public share-link to its cached event snapshot. No auth - the (eventId, token) pair from the URL is the gate, and the snapshot only contains the safe fields enumerated on `PublicEvent`. Returns null when the Redis cache has no entry for that pair (link expired, revoked, or evicted under memory pressure - caller treats all three the same).
eventId: String!token: String!
existingPublicEventLink CreatePublicEventLinkResult Look up the most recently-minted public share link for an event, if one still exists in the cache. The Share modal calls this on open so it can reuse an existing link rather than minting a fresh token every time. Returns null when no live link exists - the caller then mints one via `createPublicEventLink`. Requires `requireContentReader` (admin / analyst / viewer); pending users are blocked.
eventId: String!
eventsPendingAlert [Event!]! Events awaiting an alert (severity >= minSeverity AND no alert row yet AND a signal within the last `maxAgeHours`), oldest-first by the event's earliest-signal timestamp — the Dagster alert-stage queue. `first` caps the batch (default 100, max 500); `minSeverity` floors the severity (default 4); `maxAgeHours` bounds staleness on the latest signal's real-world time so the historical backlog / backdated backfill never alerts (default 48; 0 disables). Admin/pipeline only.
first: IntminSeverity: IntmaxAgeHours: Int
eventsByLocation [Event!]! List events by location. Returns all events whose origin, destination, or general location is within the given location (including descendants).
locationId: String!
alertsByLocation [Alert!]! List alerts by location. Returns all alerts whose event's location is within the given location (including descendants).
locationId: String!status: AlertStatus
dataSources [DataSource!]! List all data sources.
dataSource DataSource Look up a data source by ID.
id: String!
locations [Location!]! List locations, optionally filtered by hierarchy level (0 = country, 1 = state, etc.).
level: Int
location Location Look up a location by ID.
id: String!
notifications [Notification!]! List notifications, optionally filtered by status.
status: NotificationStatus
notification Notification Look up a notification by ID.
id: String!
featureFlags [FeatureFlag!]! List all feature flags.
featureFlag FeatureFlag Look up a feature flag by its unique key.
key: String!
disasterTypes [DisasterType!]! List all disaster type classifications (flat list of level-3 rows).
disasterType DisasterType Look up a disaster type by ID.
id: String!
disasterTypeHierarchy [DisasterLevel1!]! List disaster types grouped into the 3-level hierarchy (level1 > level2 > level3).
locationMetadata [LocationMetadata!]! List metadata entries for a location, optionally filtered by type. By default only the CURRENT value is returned (validTo is null). Pass current: false to include the full history.
locationId: String!type: Stringcurrent: Boolean
allLocationMetadata [LocationMetadata!]! List every locationMetadata entry of a given type across all locations. By default only current values. Pass current: false for the full history.
type: String!current: Boolean
locationMetadataHistory [LocationMetadata!]! History of a (location, type) pair - newest first. Includes the current row plus every superseded one.
locationId: String!type: String!
myApiKeys [ApiKey!]! List all API keys belonging to the authenticated user. Requires authentication.
myOrganisations [Organisation!]! List organisations the authenticated user belongs to.
organisation Organisation Look up an organisation by ID. Requires membership or global admin.
id: String!
myTeams [Team!]! List teams the authenticated user belongs to.
team Team Look up a team by ID. Requires membership or global admin.
id: String!
pendingInvites [Invitation!]! List pending invitations for an organisation. Requires org admin.
organisationId: String!
invitationByToken InvitationInfo Look up an invitation by token (public - used on accept-invite page).
token: String!
myAlertSubscriptions [AlertSubscription!]! List the authenticated user's alert subscriptions.
alertSubscriptionsByLocation [AlertSubscription!]! List all alert subscriptions for a location (admin only).
locationId: String!
crises [Crisis!]! List all crises.
crisis Crisis Look up a crisis by ID.
id: String!
pendingCrises [Crisis!]! Crises awaiting enrichment (enrichmentStatus = PENDING), oldest-first — the Dagster enrichment drain. `first` caps the batch (default 100, max 500). Admin/pipeline only.
first: Int
alertsPage AlertsPage! Paginated alerts feed with severity / location / type / date filters and explicit ordering. Use this instead of `alerts(...)` when the UI needs pages or a totalCount.
input: AlertsPageInput
eventsPage EventsPage! Paginated events feed (same filter shape as alertsPage, plus event-only options). Honours teamId as a location-scope filter.
input: EventsPageInput
signalsPage SignalsPage! Paginated signals feed with source-based filtering.
input: SignalsPageInput
entityStats EntityStats! Cross-entity stats query - returns a total plus optional buckets grouped by type / severity / day / week / month. Filter shape mirrors the page queries so a "current view" can compute its own counts.
input: EntityStatsInput!
nominatimCacheEntry NominatimCacheEntry Look up a cached Nominatim geocoder response by query hash. Returns null when the entry is missing or expired (admin/pipeline only).
queryHash: String!
activityLogs [ActivityLog!]! Paginated activity log. Admin only. Newest first.
filter: ActivityLogFilterInputlimit: Intoffset: Int
activityStats ActivityStats! Aggregated activity counts + per-user + per-day breakdown for the admin dashboard. Admin only. Default window: last 30 days.
from: DateTimeto: DateTime
userEngagement UserEngagementMetrics! Point-in-time user-engagement summary: DAU, WAU, MAU, and the DAU/MAU stickiness ratio. Derived from auth.login activity. Admin only. `asOf` defaults to NOW(); pass a historical timestamp to compute as-of that date.
asOf: DateTime
dauSeries [DauPoint!]! Daily Active Users time series - one point per UTC date in the window. Admin only. Days with zero logins are omitted; the dashboard can backfill them client-side when plotting.
from: DateTime!to: DateTime!
mauSeries [MauPoint!]! Monthly Active Users time series - one point per UTC calendar month in the window. Admin only.
from: DateTime!to: DateTime!
pipelineCountries [PipelineCountry!]! The countries the CLEAR pipeline publishes a Situation Analysis for, with each Country's bounding box. The scheduled publisher reads this to know which countries to run. Requires authentication.
translations [TranslationRow!]! Translation rows currently stored for an entity, one per locale. Admin/pipeline only. Used by clear-pipeline to compare stored source hashes against the canonical row and decide which fields to re-translate.
entityType: String!entityId: String!
pendingTranslations [TranslationQueueItem!]! Entities enqueued for (re)translation, oldest-first — the Dagster translation drain (durable replacement for the lazy-on-read Celery enqueue). Optional entityType/locale filters; `first` caps the batch (default 100, max 500). Admin/pipeline only.
first: IntentityType: Stringlocale: String
translationCoverage [TranslationCoverage!]! Per-(entityType, locale) translation coverage snapshot for the admin dashboard. Admin only. Each row reports canonicalCount (how many entities of that type exist) and translatedCount (how many have a translation row for that locale). Coverage = translated / canonical.
entitiesMissingTranslation [ID!]! IDs of entities of the given type that have NO translation row for the given locale. Admin/pipeline only. Lets the backfill driver enqueue only entities the worker would actually translate, instead of relying on per-task staleness diffs to no-op thousands of already-current rounds. Stale rows (row exists but hashes are out of date) are NOT returned here - they're rare and handled by the per-entity enrichment hooks.
entityType: String!locale: String!
resolveKnowledgebaseLocation String Resolve a knowledge-base location reference to a `locations.id`. Pcode wins over name; `adminLevel` narrows the name match so a village that shares its state's name doesn't collide. Returns null when neither the pcode nor the name matches - the ingest keeps the raw pcode on the row so a future backfill can re-resolve. A name given without `adminLevel` that matches more than one admin level is ambiguous and also returns null (rather than guessing the deepest match), so an unlevelled name never silently lands on the wrong tier. Admin / pipeline only.
pcode: Stringname: StringadminLevel: Int
resolveGazetteerLocation GazetteerHit Resolve a place name against the offline GeoNames gazetteer - the first tier of the hybrid geo-resolver. Tries an exact normalised-name match, then a `pg_trgm` fuzzy match, preferring populated places / admin areas (feature class P/A) and the more-populous tie-break. `countryCode` (ISO-3166-1 alpha-2, e.g. "SD") scopes the search; omit to search every loaded country. `minSimilarity` (default 0.4) floors the fuzzy match. Returns null on no match - the geoparser then falls back to LocationIQ for landmarks/POIs the gazetteer lacks. Admin / pipeline only.
name: String!countryCode: StringminSimilarity: Float
searchKnowledgebase [KnowledgebaseHit!]! Hybrid dense + BM25 retrieval over the knowledge base, fused with Reciprocal Rank Fusion (k=60) and returned in descending score order. Both retrievers run in parallel over the same filter set; each contributes up to 50 candidates before fusion. The embedding provider is the one configured in the environment - keep the write and read sides on the same provider or turn on `filters.currentEmbeddingModelOnly` to guarantee vector-space consistency. Requires any authenticated content reader.
query: String!filters: KnowledgebaseFilterslimit: Int
knowledgebaseIngestJob KnowledgebaseIngestJob Poll a Dagster run kicked off by `uploadKnowledgebaseDocument`. Returns null when the runId doesn't exist on this Dagster instance (e.g. Dagster was restarted with a fresh instance store, or the runId was recorded against a different DAGSTER_URL). Requires any authenticated content reader.
runId: String!
reportDatapoint ReportDatapoint One report's extracted structured datapoints. Returns null when no extraction has been persisted yet (report ingested via vector RAG but the datapoint pipeline hasn't caught up). Requires any authenticated content reader.
reportId: String!
situationAnalysis SituationAnalysis Current situation-analysis snapshot for one bucket of a country. Buckets are keyed `(countryLocationId, windowKind, windowStart)`. By default reads the yearly bucket, where `year` derives `windowStart = Jan 1` server-side and falls back to `year(now())` when null - the dashboard's usual call. Pass `windowKind` + `windowStart` instead to read a finer bucket (e.g. monthly, to diff one month against the previous). Returns null when no snapshot exists - the dashboard should render an empty state and wait for the next weekly generation. Requires any authenticated content reader.
countryLocationId: String!year: IntwindowKind: String — Bucket granularity, part of the bucket key. Defaults to `yearly`. The pipeline owns this taxonomy (currently `yearly` and `monthly`), so it is not validated against a fixed list here - an unknown value simply matches no row and returns null.windowStart: DateTime — Exact bucket start, matched for equality. Required when `windowKind` is anything other than `yearly`, because a year alone cannot identify a finer bucket. Must be the same instant the writer used (midnight UTC on the first day of the window), so pass the value the pipeline computed rather than reconstructing it. `windowEnd` is never matched on.asOf: DateTime — Historical read - return the version that was current at this timestamp (defaults to now).schemaVersion: String — Pin the read to a payload schema version. Defaults to the most recently written version for the bucket. Versions coexist rather than supersede - an older payload shape stays readable, so pass this to keep a client on a shape it understands. Read `schemaVersion` off the returned row to see what you got.
situationAnalysesForCountry [SituationAnalysis!]! Trend / history view - one row per year for a country, current versions only, newest year first. Never mixes schema versions: a bump changes what the numbers mean, so one series is always one version. Requires any authenticated content reader.
countryLocationId: String!limit: IntschemaVersion: String — Pin the trend to a payload schema version. Defaults to the country's most recently written version. Pass an older one to chart a historical payload shape.
situationAnalysisById SituationAnalysis Read one situation-analysis snapshot by its row id, including superseded history rows (the bucket-keyed `situationAnalysis` query only returns the current row). Used by the translation pipeline to fetch a specific generation's canonical prose. `data` is overlaid with the caller's locale translation like every other read — the pipeline calls it as `en` and gets canonical text back. Requires any authenticated content reader.
id: String!
hasAggregatedDatapoints Boolean! True when at least one current `aggregated_datapoints` row exists for the given schema version. Used by the Dagster aggregation asset to distinguish first-run backfill (wide lookback window) from routine weekly refreshes (narrow window). Pass `countryLocationId` (an admin-0 location id) to scope the check to ONE country, so a newly-onboarded country's first run uses the initial window even after other countries are established — the four-tier walk always produces yearly/all-time buckets AT the country location, so an A0 row is the per-country signal. Any authenticated content reader - a cheap existence check.
schemaVersion: String!countryLocationId: String
aggregatedDatapoint AggregatedDatapoint Aggregated datapoints for a (window × window_kind × location) scope. Cache-first: returns the pre-computed snapshot when one is current (`validTo IS NULL` or covers the `asOf` timestamp); otherwise assembles the bucket on-demand from `report_datapoints` and returns it with `onDemand = true`. Returns null when no contributing reports exist in scope.
locationId: String — Null = country-wide roll-up (yearly / all-time tiers).windowStart: DateTime!windowEnd: DateTime!windowKind: String!schemaVersion: String — Defaults to the currently-configured pipeline schema version.asOf: DateTime — Historical snapshot lookup - return the version that was current at this timestamp. Defaults to `now`.
webhookSubscriptions [WebhookSubscription!]! All webhook subscriptions, newest first.
webhookSubscription WebhookSubscription One subscription by ID, or null if not found.
id: String!
webhookDeliveries [WebhookDelivery!]! Recent delivery attempts for a subscription, newest first.
subscriptionId: String!limit: Int
groundSources [GroundSource!]! All ground sources (per-source policy records), newest first.
groundThreads [GroundThread!]! Review queue: ground threads, newest first. Filter by source and/or review state ("unverified" | "approved_private" | "approved_public" | "rejected").
groundSourceId: StringreviewState: Stringlimit: Intoffset: Int
groundThread GroundThread One ground thread with its messages, or null if not found.
id: String!
groundMessages [GroundMessage!]! Staged messages, oldest first. Filter by source and/or thread.
groundSourceId: StringthreadId: Stringlimit: Intoffset: Int
groundMessagesForClassification [GroundMessageForClassification!]! PIPELINE CONTRACT (admin/pipeline only): a source's staged messages, oldest first, projected for the classification/threading worker — no private-tier sender identity. Returns ALL messages (classified and not) so one query powers both labelling and thread assembly (clustering staged Signals into threads).
groundSourceId: String!limit: Int
groundThreadsForSource [GroundThread] PIPELINE CONTRACT (admin/pipeline only): threading context for the classification worker — a source's existing threads, oldest first, so late corrections/retractions can target an existing thread via GroundThreadUpsertInput.threadId. `states` filters on lifecycleState ("reported" | "updated" | "confirmed" | "corrected" | "retracted"); omitted/empty returns all. The worker selects {id, title, lifecycleState, reviewState, messageIds} — no message content, no sender identity.
groundSourceId: String!states: [String!]

Mutations

NameReturnsDescription
createApiKey CreateApiKeyPayload! Create a new API key for the authenticated user.
input: CreateApiKeyInput!
revokeApiKey ApiKey! Revoke an API key by ID. Only the key owner or an admin can revoke.
id: String!
createDevUser CreateDevUserResult! Provision a developer account from an approved waitlist application. Creates the user, mints an initial API key (no expiry), issues a long-lived set-password verification token, and sends the welcome email. Requires global `admin`. The CRM write-back is the caller's responsibility.
input: CreateDevUserInput!
rotateDevUserApiKey RotateDevUserApiKeyResult! Revoke every active API key for a dev user and issue a fresh one. Notifies the user by email. Requires global `admin`.
userId: String!
approveUser ApproveUserResult! Approve a self-signed-up user. Flips their role from `pending` to `viewer` (granting read access to signals / events / alerts / crises) and moves their CRM contact from the prospects collection into the approved collection — which fires Exponential's welcome automation. The local role flip is authoritative; CRM updates are best-effort and surface as fields on the result so the admin UI can offer a retry. Requires global `admin`.
userId: String!
requestEmailVerification Boolean! Request an email verification link for the authenticated user.
verifyEmail Boolean! Verify email using a token from the verification link.
token: String!
updateProfile User! Update the authenticated user's profile and notification preferences.
input: UpdateProfileInput!
createAlert Alert! Create an alert from an event, notifying subscribers.
input: CreateAlertInput!
updateAlert Alert! Update an existing alert.
id: String!input: UpdateAlertInput!
deleteAlert Boolean! Delete an alert.
id: String!
archiveStaleAlerts ArchiveStaleAlertsResult! Archive published alerts whose event.lastSignalCreatedAt is older than olderThanDays (default: 14). Sets alerts.status to 'archived'. Admin or pipeline only. Returns the number of rows affected.
olderThanDays: Int
createSignal Signal! Create a signal from a data source.
input: CreateSignalInput!
markSignalsProcessed Int! Mark signals as done for the Dagster drain — the downstream pipeline calls this after classify→group→alert. Sets status (PROCESSED by default, or FAILED) and processedAt. Returns the number of rows updated. Idempotent. Admin/pipeline only.
ids: [String!]!status: SignalStatus
createManualSignal Signal! Create a manual signal from a field officer, partner, or government source. Persists the signal and sends it to the pipeline for event grouping and auto-escalation.
input: CreateManualSignalInput!
updateSignalSeverity Signal! Update a signal's severity score.
id: String!severity: Int!
updateSignalGeoparsedData Signal! Attach the clear-pipeline geoparser's result to an existing signal. Used for the manual-signal flow, where the signal is created via createManualSignal before the pipeline has a chance to run the geoparser. Stores the structured candidate verbatim; does not change locationId. Admin/pipeline only.
id: String!geoparsedData: JSON!
updateSignalLocation Signal! Set (or replace) an existing signal's generalLocation. Used by the manual-signal pipeline path: when the user didn't pick a location and the geoparser resolved a landmark, we promote it to an L4 via findOrCreateLandmarkL4 and then wire the signal to it so downstream event grouping can key on the resolved admin-2 district instead of creating an isolated event. Admin/pipeline only.
id: String!locationId: String!
deleteSignal Boolean! Delete a signal.
id: String!
submitSignalLocationChallenge SignalLocationChallenge! Create or replace the open (consideration) Location challenge for a Signal. Auth: any approved logged-in team member who can view the Signal. Queue only — does NOT accept/reject and does NOT mutate the Signal's geometry.
input: SubmitSignalLocationChallengeInput!
createEvent Event! Create a new event from signals.
input: CreateEventInput!
updateEvent Event! Update an existing event.
id: String!input: UpdateEventInput!
deleteEvent Boolean! Delete an event.
id: String!
escalateEvent EventEscalation! Escalate an event: creates an alert (published) and records the user escalation. If the event already has a published alert, just records the user escalation. teamId (optional) admits a team_admin or field_coordinator on that team even without a global admin/analyst role — purely an authorisation hint, not stored on the event.
eventId: String!userId: String!teamId: String
createDataSource DataSource! Create a new data source.
input: CreateDataSourceInput!
updateDataSource DataSource! Update an existing data source.
id: String!input: UpdateDataSourceInput!
deleteDataSource Boolean! Delete a data source.
id: String!
resolveDataSource String! Idempotently resolve an organisation/source name to a data_sources id, creating an ungraded row if none matches (admin/pipeline only). Matching order: exact name/synonym → infoUrl (when homepage given) → pg_trgm fuzzy (>= minSimilarity, default 0.6) → create. On a URL/fuzzy hit the incoming name is appended as a synonym so future lookups hit exactly. Returns the resolved data_sources id. See clear-context-pipeline ADR-0004.
name: String!homepage: StringminSimilarity: Float
createLocation Location! Create a new location.
input: CreateLocationInput!
ensureCountryLocation Location! Idempotently resolve a level-0 Country location by exact name, creating it with a bounding-box MULTIPOLYGON geometry if absent (admin/pipeline only). bbox is [minLng, minLat, maxLng, maxLat]. Returns the (found or created) Country — doubles as the pipeline's name→id resolution.
name: String!bbox: [Float!]!
updateLocation Location! Update an existing location.
id: String!input: UpdateLocationInput!
deleteLocation Boolean! Delete a location.
id: String!
updateLocationGeometry Location! Replace a location's geometry with the given GeoJSON (admin/pipeline only).
id: String!geometry: GeoJSON!
updateLocationPopulation Location! Set a location's cached population (admin/pipeline only).
id: String!population: String!
updateCrisisPopulation Crisis! Set a crisis's populationAffected + populationInArea (admin/pipeline only).
id: String!input: UpdateCrisisPopulationInput!
upsertLocationMetadata LocationMetadata! Create or update a location's metadata entry for a given type (admin/pipeline only). Upsert keyed by (locationId, type).
input: UpsertLocationMetadataInput!
upsertLocationMetadataBatch [LocationMetadata!]! Bulk-upsert multiple (locationId, type, data) rows in a single call (admin/pipeline only). Returns the current row for each input. Rows whose locationId doesn't exist are skipped silently. Idempotent: an input whose blob is identical to the currently-open row is left untouched (no new history version), so re-running an ingest with unchanged data is a no-op.
inputs: [UpsertLocationMetadataInput!]!
deleteLocationMetadata Boolean! Delete a location's metadata entry for a given type (admin only).
locationId: String!type: String!
findOrCreateLandmarkL4 FindOrCreateLandmarkL4Result! Find an existing level-4 location matching a geoparsed candidate, or create one. Used by the clear-pipeline geoparser to promote a landmark hit (e.g., "Nyala Airport") into a reusable A4 instead of letting the resolver invent a fresh point-location for every signal. When sourceLat and sourceLng are provided, the resolver verifies that the candidate's containing A2 matches the source coord's containing A2 — on mismatch it aborts with abortedReason="different_a2" so the caller can fall back to source coords. Admin/pipeline only.
input: FindOrCreateLandmarkL4Input!
upsertNominatimCache NominatimCacheEntry! Upsert a Nominatim geocoder cache entry (admin/pipeline only). Replaces any existing row with the same queryHash, resetting the TTL.
input: UpsertNominatimCacheInput!
setFeatureFlag FeatureFlag! Set the enabled state of a feature flag, identified by its string key. Upserts the row so the same call works whether the key is already present. Admin only — toggling features is an org-wide change, not a per-user preference. Returns the persisted flag so callers can update local state without an extra round-trip.
key: String!enabled: Boolean!
createNotification Notification! Create a notification for a user.
input: CreateNotificationInput!
createBulkNotifications Int! Create notifications for multiple users at once. Returns the count of notifications created.
input: CreateBulkNotificationsInput!
notifyAlertSubscribers Int! Notify all subscribers of a single alert (immediate frequency). Matches on event types and locations.
input: AlertNotifyInput!
notifyAlertDigest Int! Send a digest notification for multiple alerts to subscribers of the given frequency (daily/weekly/monthly).
input: AlertDigestInput!
deleteNotification Boolean! Delete a notification.
id: String!
markNotificationRead Notification! Mark a notification as read.
id: String!
markAllNotificationsRead Boolean! Mark all notifications as read for the authenticated user.
addFeedback UserFeedback! Add feedback (rating + optional text) to a signal or event.
input: AddFeedbackInput!
deleteFeedback Boolean! Delete your own feedback.
id: String!
addComment UserComment! Add a comment to a signal or event.
input: AddCommentInput!
replyToComment UserComment! Reply to an existing comment.
input: ReplyToCommentInput!
deleteComment Boolean! Delete your own comment.
id: String!
tagUsersInComment UserComment! Tag users in a comment.
commentId: String!userIds: [String!]!
createOrganisation Organisation! Create a new organisation. The creator becomes the first org_admin.
input: CreateOrganisationInput!
updateOrganisation Organisation! Update an existing organisation. Requires org_admin (or platform admin).
id: String!input: UpdateOrganisationInput!
addOrgMember OrgMember! Add a member to an organisation.
orgId: String!userId: String!role: OrgMemberRole
removeOrgMember Boolean! Remove a member from an organisation.
orgId: String!userId: String!
updateOrgMemberRole OrgMember! Change a member's role within an organisation. Requires the caller to be a platform admin or an org_admin of the target organisation.
orgId: String!userId: String!role: OrgMemberRole!
deleteOrganisation Boolean! Delete an organisation and all its teams, members, and invitations. Requires global admin.
id: String!
createTeam Team! Create a new team within an organisation. Requires org_admin (or platform admin).
input: CreateTeamInput!
updateTeam Team! Update an existing team.
id: String!input: UpdateTeamInput!
deleteTeam Boolean! Delete a team.
id: String!
addTeamMember TeamMember! Add a member to a team.
teamId: String!userId: String!role: TeamMemberRole
removeTeamMember Boolean! Remove a member from a team.
teamId: String!userId: String!
updateTeamMemberRole TeamMember! Update a team member's role.
teamId: String!userId: String!role: TeamMemberRole!
setTeamLocations Team! Set the locations a team is scoped to. Replaces all existing locations.
teamId: String!locationIds: [String!]!
setDefaultTeam Team! Set the authenticated user's default team (for frontend convenience).
teamId: String!
inviteUser Invitation! Invite a user to an organisation (and optionally a team). Sends invite email.
input: InviteUserInput!
acceptInvite Boolean! Accept an invitation. Creates user account if new, adds to org and team.
input: AcceptInviteInput!
cancelInvite Boolean! Cancel a pending invitation.
id: String!
resendInvite Invitation! Resend an invitation email (resets expiry to 7 days).
id: String!
requestPasswordReset Boolean! Request a password reset email (public, always returns true).
email: String!
resetPassword Boolean! Reset password using a token from the reset email.
token: String!newPassword: String!
subscribeToAlerts AlertSubscription! Subscribe to alerts for a specific type and location.
input: SubscribeToAlertsInput!
subscribeToAlertsBatch [AlertSubscription!]! Subscribe to alerts for multiple (location × alertType) combinations in a single call. Returns the list of created subscriptions. Duplicates are skipped silently.
input: SubscribeToAlertsBatchInput!
updateAlertSubscription AlertSubscription! Update an existing alert subscription (channel, frequency, active).
id: String!input: UpdateAlertSubscriptionInput!
unsubscribeFromAlerts Boolean! Unsubscribe - deletes the subscription.
id: String!
createCrisisFromEvents Crisis! Create a new crisis from a list of event IDs. Links all provided events to the new crisis.
input: CreateCrisisFromEventsInput!
markCrisisEnriched Crisis! Mark a crisis ENRICHED for the Dagster drain — the enrichment consumer calls this once narrative/scenarios/needs-analysis are current. Idempotent. Admin/pipeline only.
id: String!
addEventToCrisis EventCrisis! Add an existing event to an existing crisis. Idempotent - returns the existing link if one already exists.
crisisId: String!eventId: String!
removeEventFromCrisis Crisis Remove an event from a crisis. Recomputes populationAffected from the remaining events and dispatches the enrichment task so title/summary get regenerated to reflect the new event set. If the event being removed is the LAST event, deletes the crisis entirely and returns null. Otherwise returns the updated crisis (title/summary still show pre-removal values until the async enrichment task completes).
crisisId: String!eventId: String!
updateCrisisTitle Crisis! Edit a crisis's title in place. Any authenticated user. Pass an empty string to clear the field.
id: String!title: String!
updateCrisisDescription Crisis! Edit the human-facing description on a crisis. The crisis's summary column stores JSON of the form description+tldr — this mutation updates just the description key and preserves any existing tldr bullets (which the LLM enrichment task generates). Any authenticated user. Pass an empty string to clear the description without disturbing the tldr.
id: String!description: String!
deleteCrisis Boolean! Delete a crisis. Cascades the eventCrises join rows, user feedback, and user comments via the FK constraints. Any authenticated user.
id: String!
addCrisisAttachments Crisis! Append S3 keys to a crisis's attachments list. Idempotent — keys already present in the list are skipped silently. Returns the updated crisis with the new list.
id: String!keys: [String!]!
removeCrisisAttachment Crisis! Remove an S3 key from a crisis's attachments list. Does NOT delete the underlying S3 object (operators can clean those up separately). Returns the updated crisis.
id: String!key: String!
setCrisisNeedsAnalysis Crisis! Set the LLM-generated NRC SAF needs analysis inside the crisis's needs JSONB. Merges generalSummary and sector keys into the existing object so other keys on needs are preserved. Admin/pipeline only.
id: String!generalSummary: [String!]!sector: JSON!
upsertTranslations UpsertTranslationsResult! Upsert one or more per-locale translation rows for an event, crisis, or location. The translated data blob mirrors the canonical entity's JSON shape per locale. Admin/pipeline only. Clears any matching rows from the translation queue (the write is the drain-completion signal).
input: UpsertTranslationsInput!
enqueueTranslation TranslationQueueItem! Enqueue an entity for (re)translation at a locale — the durable replacement for the lazy-on-read Celery enqueue. Idempotent (one row per entity+locale). Admin/pipeline only.
entityType: String!entityId: String!locale: String!
markTranslated Boolean! Remove an entity/locale from the translation queue (explicit drain completion). Returns true when a queued row was removed. upsertTranslations clears the queue itself, so this is only for consumers not writing via it. Admin/pipeline only.
entityType: String!entityId: String!locale: String!
upsertKnowledgebaseChunks UpsertKnowledgebaseResult! Replace all knowledgebase rows for `reportId` with `chunks`. Runs inside one transaction — a failed insert rolls back the delete so a re-run can retry cleanly. Vector length is validated against the pgvector column dimension (1024) on the server; mismatched rows are rejected before the write hits Postgres. Admin/pipeline only.
reportId: String!reportTitle: String!sourceUrl: String!s3Key: String!publishedAt: DateTime!chunks: [KnowledgebaseChunkInput!]!
upsertReportDatapoints UpsertReportDatapointsResult! Replace the `report_datapoints` row for `input.reportId`. Admin / pipeline only. The dagster-quickstart datapoints extraction asset is the primary caller; hand-invocation is supported for backfills and re-extraction. Idempotent — the row is uniquely keyed on `report_id`.
input: UpsertReportDatapointsInput!
upsertSituationAnalysis UpsertSituationAnalysisResult! Insert a fresh situation-analysis snapshot for (`input.countryLocationId`, `input.windowStart`, `input.windowEnd`) and stamp `validTo = now()` on the previous current row for the same bucket in the same transaction. History rows are preserved. Admin / pipeline only — the Dagster `weekly_situation_analyses` asset is the primary caller.
input: UpsertSituationAnalysisInput!
refreshAggregatedDatapoints RefreshAggregatedDatapointsResult! Pre-compute all four aggregation tiers (weekly × A2, monthly × A1, yearly × country, all-time × country) for reports whose `reportingPeriodEnd` falls in `[from, to]`. Each computed bucket is inserted with `validFrom = now()`; the previous "current" row for the same bucket key has its `validTo` stamped in the same transaction. History rows are preserved. Admin / pipeline only. Pass `countryLocationId` (an admin-0 location id) to SCOPE the refresh to that country's subtree — only that country's buckets are (re)computed (the window's reports are still scanned; the bucket step filters to scopes whose admin-0 ancestor is this country). Omit it to refresh every country in the window (the original global behaviour). The per-country partitioned pipeline passes it so each country's run recomputes only its own buckets instead of a redundant global pass. An unresolvable id errors rather than silently computing nothing.
from: DateTime!to: DateTime!schemaVersion: String!countryLocationId: String
uploadKnowledgebaseDocument KnowledgebaseIngestJob! Upload a PDF into the manual-ingest S3 inbox and trigger the Dagster `process_manual_document_job` to run the extract → chunk → enrich → embed → upsert chain against it. The report_id is derived from the file's SHA-256 (first 12 chars, prefixed `manual:`) so re-uploading the same bytes is idempotent — Dagster runs but the upsert path replaces the previous version in place. To force a fresh row, tweak the file (any byte) or switch to a scripted upload that supplies its own report_id. Restricted to admin / analyst — the ingest costs LLM + embedding credits per document. When DAGSTER_URL is unset the mutation still stages the PDF in S3 but returns a UNKNOWN-status job with no runId — useful for offline dev of the upload path.
file: Upload!title: String!sourceUrl: StringpublishedAt: DateTime!
createGroundSource GroundSource! Create a ground source — the per-source policy record (consent scope, privacy default, reviewer roles, retention) that every ingest from a WhatsApp group or hotline is gated on. Admin only; group kinds require a complete consent record (consentScope + consentRecordedAt + consentRecordedBy).
input: CreateGroundSourceInput!
updateGroundSource GroundSource! Update a source's policy record (admin only, partial). The merged row is re-validated — a group-kind source cannot be left without a complete consent record, so legacy rows must have consent supplied in the same update. transportId is immutable.
id: String!input: UpdateGroundSourceInput!
setGroundSourceActive GroundSource! Activate or deactivate a source (admin only). Deactivation is the live-capture kill switch: the ingest consent gate rejects every payload for an inactive source. Deliberately exempt from consent-record validation so an incomplete legacy row can still be shut off immediately.
id: String!isActive: Boolean!
reviewGroundThread GroundThread! Review a ground thread: decision is "approve_private", "approve_public", or "reject". Role-gated per source (the caller's global role must appear in the source's reviewerRoles; platform admins always pass). Transitions follow the V1 state machine — notably approved_public is terminal. approve_public also promotes the thread into the standard signals graph via createSignal, with all sender identity scrubbed.
id: String!decision: String!note: String
upsertGroundMessageClassifications Int! PIPELINE CONTRACT (admin/pipeline only): write back classifications from the classify_ground_messages worker. Unknown messageIds are skipped with a warning. Returns the number of messages updated.
inputs: [GroundMessageClassificationInput!]!
upsertGroundThreads [String]! PIPELINE CONTRACT (admin/pipeline only): replace placeholder threading with pipeline-built threads (clusters of staged Signals). Each input creates a thread (or, when `threadId` is set, APPENDS to that existing thread and updates its lifecycleState + title), re-points its messageIds at it, and deletes the placeholder threads that became empty. Messages whose current thread has already been human-reviewed (or promoted) are NEVER re-threaded, and a promoted `threadId` target is never mutated (a new thread is created instead) — the review gate outranks the pipeline. Returns one thread id per input, in order; null where an input had no movable messages (no thread created or updated).
inputs: [GroundThreadUpsertInput!]!
createWebhookSubscription WebhookSubscription! Create a new webhook subscription. The response is the only place the plaintext secret is returned — persist it in your own records (e.g. downstream verifier config) at this moment. To retrieve later, use rotateWebhookSubscriptionSecret which generates a fresh one.
input: CreateWebhookSubscriptionInput!
updateWebhookSubscription WebhookSubscription! Update a subscription. Only provide fields you want to change.
id: String!input: UpdateWebhookSubscriptionInput!
deleteWebhookSubscription Boolean! Permanently delete a subscription and all its delivery history.
id: String!
rotateWebhookSubscriptionSecret WebhookSubscription! Generate a new secret and invalidate the old one. Response includes the new plaintext secret (same one-shot semantics as create).
id: String!
sendTestWebhookEvent WebhookDelivery! Send a synthetic test event to this subscription. Creates a WebhookDelivery row and attempts delivery immediately. Payload mimics GlitchTip's alert format so downstream verifiers see a realistic shape.
id: String!
retryWebhookDelivery WebhookDelivery! Re-fire a dead-lettered delivery. Resets attemptNumber to 1 and schedules an immediate retry via the poller.
id: String!

Types

All types in the schema, auto-generated from the running server.

DateTime scalar

ISO 8601 date-time string (e.g. 2024-01-15T09:30:00.000Z).

GeoJSON scalar

JSON scalar

Arbitrary JSON value — objects, arrays, strings, numbers, booleans, or null.

Upload scalar

File upload scalar (via graphql-upload).

AlertOrderBy enum

ValueDescription
CREATED_DESCNewest first by event.firstSignalCreatedAt.
CREATED_ASCOldest first by event.firstSignalCreatedAt.
SEVERITY_DESCHighest event severity first.
SEVERITY_ASCLowest event severity first.

AlertStatus enum

Publication status of an alert.

ValueDescription
draft
published
archived

Channel enum

Notification channel for alert subscriptions.

ValueDescription
email
sms

CrisisEnrichmentStatus enum

Durable enrichment status for the Dagster drain. PENDING = needs (re)enrichment (set on crisis create / event add / event remove); ENRICHED = narrative/scenarios/needs-analysis current.

ValueDescription
PENDING
ENRICHED

DetectionStatus enum

Processing status of a detection (retained for potential future use).

ValueDescription
raw
processed
ignored

EntityKind enum

ValueDescription
signal
event
alert

EventOrderBy enum

ValueDescription
LAST_SIGNAL_DESCNewest signal first (lastSignalCreatedAt).
LAST_SIGNAL_ASCOldest signal first (lastSignalCreatedAt).
CREATED_DESCNewest first by firstSignalCreatedAt.
CREATED_ASCOldest first by firstSignalCreatedAt.
SEVERITY_DESC
SEVERITY_ASC

Frequency enum

How often a user receives alert notifications.

ValueDescription
immediately
daily
weekly
monthly

InvitationStatus enum

Status of an invitation.

ValueDescription
pending
accepted
expired

KnowledgebaseIngestStatus enum

Coarse-grained status of a manual-ingest Dagster run. Dagster's own RunStatus enum is finer-grained (STARTING / MANAGED / CANCELING / …) — we fold those down to what a UI actually needs to render.

ValueDescription
QUEUED
STARTED
SUCCESS
FAILURE
CANCELED
UNKNOWNDagster is offline, the run id doesn't exist, or the run status is one this API doesn't recognise. Non-terminal — poll again.

NotificationStatus enum

ValueDescription
PENDING
DELIVERED
FAILED
READ

OrgMemberRole enum

Role within an organisation.

ValueDescription
org_admin
member

SignalOrderBy enum

ValueDescription
PUBLISHED_DESCNewest first by publishedAt.
PUBLISHED_ASCOldest first by publishedAt.
SEVERITY_DESC
SEVERITY_ASC

SignalStatus enum

Durable processing status for the Dagster event-driven drain. NEW = ingested, awaiting downstream processing; PROCESSED = classify→group→ alert done; FAILED = terminal failure.

ValueDescription
NEW
PROCESSED
FAILED

StatsGroupBy enum

ValueDescription
noneSingle bucket — just `total`. Use this for "how many X" queries.
typeGroup by event/signal type (event.types[] is unnested; signals use their source name as the type proxy).
severityGroup by integer severity (1-5).
dayGroup by day / week / month of the entity's primary timestamp. Buckets are returned with ISO-8601 keys (`YYYY-MM-DD`, `YYYY-Www`, `YYYY-MM`).
week
month

TeamMemberRole enum

Role within a team.

ValueDescription
team_admin
field_coordinator
team_member

WebhookDeliveryStatus enum

ValueDescription
pendingWaiting for the first attempt (rare — the receive route usually attempts inline).
succeededDelivered — target returned 2xx on some attempt.
retryingFailed at least once; a retry is scheduled.
deadExhausted all retry attempts. Admin can manually re-fire.

AcceptInviteInput input

FieldTypeDescription
token String!
name String!
password String!

ActivityLogFilterInput input

Filter for the paginated activityLogs query.

FieldTypeDescription
userId String
actionPrefix String Action prefix match — e.g. 'crisis.' returns all crisis-related rows.
action String Exact action match.
resourceType String Coarse resource bucket — 'signal' / 'event' / 'alert' / 'crisis' / 'feedback' / 'session'.
from DateTime
to DateTime

AddCommentInput input

FieldTypeDescription
eventId String Provide exactly one of eventId, signalId, or crisisId.
signalId String
crisisId String
comment String!
tagUserIds [String!] User IDs to tag in the comment.

AddFeedbackInput input

FieldTypeDescription
eventId String Provide exactly one of eventId, signalId, or crisisId.
signalId String
crisisId String
rating Int! Rating from 1 to 5.
text String Optional textual feedback.

AlertDigestInput input

FieldTypeDescription
alertIds [String!]! List of alert IDs to include in the digest.
frequency String! Frequency: daily, weekly, or monthly.

AlertNotifyInput input

FieldTypeDescription
alertId String! Alert ID to notify subscribers about (uses immediate frequency).

AlertsPageInput input

FieldTypeDescription
limit Int Page size — clamped to [1, 100]. Default 25.
offset Int Zero-based row offset. Default 0.
orderBy AlertOrderBy
status AlertStatus
teamId String Apply a team's location-scope filter to the underlying events.
locationId String Restrict to alerts whose event sits under this location (or any of its descendants).
eventTypes [String!] Glide codes — alert event must contain at least one of these in its `types` array. Case-sensitive.
severityMin Int Inclusive lower bound on event severity (1-5).
severityMax Int Inclusive upper bound on event severity (1-5).
from DateTime Filter on event.firstSignalCreatedAt — inclusive.
to DateTime Filter on event.firstSignalCreatedAt — inclusive.
includeDummy Boolean Hide isDummy events when false (default).

CreateAlertInput input

FieldTypeDescription
eventId String! The event ID to create an alert from.
status AlertStatus

CreateApiKeyInput input

Input for creating a new API key.

FieldTypeDescription
name String! A descriptive name for this key (e.g. my-app-prod).
expiresAt DateTime Optional expiration date. Omit for a key that never expires.

CreateBulkNotificationsInput input

FieldTypeDescription
userIds [String!]! List of user IDs to notify.
message String!
notificationType String!
actionUrl String
actionText String

CreateCrisisFromEventsInput input

FieldTypeDescription
title String
summary String
severity Float!
locationId String
needs JSON! Needs as JSON.
eventIds [String!]! Event IDs to link to the newly created crisis (must not be empty).
teamId String Team the crisis is being filed under. When present, the caller may be a team-level team_admin or field_coordinator on that team instead of a platform admin/analyst. Purely an authorisation hint — the crisis itself has no team column; team affiliation is derived from location scope. Ignored for platform-level callers.

CreateDataSourceInput input

FieldTypeDescription
name String!
type String!
isActive Boolean
baseUrl String
infoUrl String
synonyms [String!] Alias set for source-name normalisation (see resolveDataSource).
reliability Int NATO Admiralty-style reliability grade 1–4; null = ungraded.

CreateDevUserInput input

Input for `createDevUser`.

FieldTypeDescription
email String! The dev's email address — also the dedup key against existing users.
name String! Display name shown in the welcome email and in the User row.
keyName String Optional descriptive label for the first API key. Defaults to "Initial dev key".

CreateEventInput input

FieldTypeDescription
signalIds [String!]!
title String
description String
descriptionSignals JSON
validFrom String!
validTo String!
firstSignalCreatedAt String!
lastSignalCreatedAt String!
originId String
destinationId String
locationId String
types [String!]!
severity Int Severity score (1–5). Aggregated from signal severities.
populationAffected String
populationDisplaced String Estimated population displaced (BigInt as string).
casualties Int Aggregated casualties for the event (max across constituent signals).
rank Float!
lat Float Latitude for automatic geo-resolution (resolves to nearest location in hierarchy).
lng Float Longitude for automatic geo-resolution.
teamId String Team the event is being filed under. When present, the caller may be a team-level team_admin or field_coordinator on that team instead of a platform admin/analyst. Purely an authorisation hint — the event itself has no team column; team affiliation is derived from location scope. Ignored for platform-level callers.

CreateGroundSourceInput input

FieldTypeDescription
name String!
kind String! "staff_group" | "partner_group" | "hotline".
transportId String! WhatsApp group JID (or hotline number). Must be unique.
consentScope String REQUIRED (with consentRecordedAt + consentRecordedBy) for the group kinds; hotline consent is explicit by design.
consentRecordedAt String
consentRecordedBy String
privacyDefault String Defaults to "private".
reviewerRoles [String!] Defaults to ["admin", "analyst"].
retentionRule String

CreateLocationInput input

FieldTypeDescription
geoId Int
osmId String
pCode String
name String!
level Int!
parentId String

CreateManualSignalInput input

FieldTypeDescription
sourceId String! Data source ID (must be field_officer, partner, or government type).
title String!
description String!
severity Int Severity score (1–5).
url String URL or reference link.
mediaUrls [String!] Media URLs (pre-uploaded via /api/upload endpoint).
media [Upload!] Media files (direct upload via graphql-upload, alternative to mediaUrls).
locationId String
originId String
destinationId String
lat Float Latitude for automatic geo-resolution.
lng Float Longitude for automatic geo-resolution.
metadata JSON Arbitrary internal metadata stored in rawData (e.g. notes, recommendAlert). Not surfaced in the UI - use freely without schema changes.
teamId String Team the signal is being filed under. When present, the caller may be a team-level team_admin or field_coordinator on that team instead of a platform admin/analyst. Purely an authorisation hint — the signal itself has no team column; team affiliation is derived from location scope. Ignored for platform-level callers.

CreateNotificationInput input

FieldTypeDescription
userId String!
message String!
notificationType String!
actionUrl String
actionText String

CreateOrganisationInput input

Fields for creating a new organisation.

FieldTypeDescription
name String! Display name for the organisation.
slug String! URL-friendly identifier. Must be unique.

CreatePublicEventLinkInput input

Input for the `createPublicEventLink` mutation.

FieldTypeDescription
eventId String! The event to share. Caller must be able to read it via the normal `event(id)` resolver — i.e. `requireContentReader` passes.
ttlDays Int Snapshot lifetime in days. Defaults to 30, capped at 90. Smaller values produce shorter URLs by reducing the impact of any future URL-history scraping.

CreateSignalInput input

FieldTypeDescription
sourceId String!
externalId String Stable upstream identifier for idempotent ingestion. If a signal with the same (sourceId, externalId) already exists, createSignal returns the existing row instead of creating a duplicate. Recommended prefix scheme: "dataminr:{alertId}", "gdacs:{eventid}", "acled:{event_id_cnty}".
rawData JSON!
rawS3Key String Pointer to the raw payload blob in the S3 data lake (bronze layer), written by the Dagster ingest asset. Optional — rawData carries the payload for sources not yet landing in the lake.
publishedAt String!
collectedAt String
url String
title String
description String
severity Int Severity score (1–5). From data source or estimated by pipeline.
casualties Int Reported casualties for the signal.
media [String!] Media URLs (source URLs for images, videos, etc.).
originId String
destinationId String
locationId String
lat Float Latitude for automatic geo-resolution (resolves to nearest location in hierarchy).
lng Float Longitude for automatic geo-resolution.
geoparsedData JSON Optional output of clear-pipeline's text-based geoparser. Additive enrichment, stored verbatim for downstream comparison against the source's coords. Schema documented on the signals model.
pointName String Human-readable name to use when the resolver has to create a new L4 point location from `lat`/`lng`. Typically the geoparser's top extracted candidate suffixed with " (unresolved)" when the Nominatim lookup failed, so audit views show `al-Obeid (unresolved)` instead of the signal's full paragraph. Ignored when `locationId` is supplied. When omitted, the resolver falls back to a coord-based label like `Point 15.6280, 30.2156`.

CreateTeamInput input

FieldTypeDescription
organisationId String!
name String!
slug String!
description String

CreateWebhookSubscriptionInput input

Input for creating a new webhook subscription. The secret is generated server-side (openssl rand -hex 32 equivalent) and returned once on create.

FieldTypeDescription
name String!
targetUrl String!
eventTypeFilter [String!] Empty array = fire on all events. Otherwise, only fire when the source payload's event type matches one of these values.
active Boolean Whether the subscription starts active. Defaults to true.

DateRangeInput input

Half-open date window (from inclusive, to exclusive) used to filter `searchKnowledgebase` by the chunk's extracted event window. Chunks whose time_range overlaps the window match.

FieldTypeDescription
from DateTime
to DateTime

EntityStatsInput input

FieldTypeDescription
entity EntityKind!
groupBy StatsGroupBy
teamId String
locationId String
eventTypes [String!]
severityMin Int
severityMax Int
from DateTime
to DateTime
includeDummy Boolean

EventsPageInput input

FieldTypeDescription
limit Int
offset Int
orderBy EventOrderBy
teamId String
locationId String
eventTypes [String!]
severityMin Int
severityMax Int
from DateTime Filter on event.firstSignalCreatedAt — inclusive.
to DateTime
includeDummy Boolean

FindOrCreateLandmarkL4Input input

Input for findOrCreateLandmarkL4. Drives geoparser-based L4 promotion in the signal-ingestion pipeline.

FieldTypeDescription
name String! Display name to use when creating a new L4 (e.g., 'Nyala Airport'). Reuse for kind='landmark' is keyed on a case-insensitive match of this field within the candidate's containing A2.
lat Float! Candidate latitude (from the geocoder).
lng Float! Candidate longitude (from the geocoder).
kind String! 'landmark' or 'admin'. Drives the reuse strategy: landmarks reuse by exact name, admins reuse by proximity (≤100m) within the same A2.
sourceLat Float Source coordinate's latitude. When provided, the resolver verifies the candidate's A2 matches the source's A2 — a mismatch aborts the promotion.
sourceLng Float Source coordinate's longitude. See sourceLat.

GroundMessageClassificationInput input

One classification write-back from the pipeline worker.

FieldTypeDescription
messageId String!
classification String! "field_report" | "news_digest" | "operational" | "chatter".
uncertaintyMarker String Pipeline-detected uncertainty tag. Null/omitted leaves the ingest-extracted marker untouched.

GroundThreadUpsertInput input

One thread (a cluster of staged Signals) produced by the pipeline threading task. Its messageIds are re-pointed at the thread, replacing their V1 one-per-message placeholder threads. With `threadId` set, the input APPENDS to that existing thread (cross-run threading: late corrections/retractions join the thread they belong to) instead of creating a new one.

FieldTypeDescription
groundSourceId String!
title String!
lifecycleState String! "reported" | "updated" | "confirmed" | "corrected" | "retracted".
messageIds [String!]!
threadId String Optional target thread for cross-run appends. When set, messageIds are appended to this thread and its lifecycleState + title are updated — provided the thread is not yet promoted (reviewState != "approved_public" and no promotedSignalId) and belongs to groundSourceId. A promoted/terminal (or unknown/wrong-source) target is never mutated: a NEW thread is created instead, with a warning.

InviteUserInput input

FieldTypeDescription
email String!
organisationId String!
role String Organisation role: org_admin, member (default: member).
teams [TeamAssignmentInput!]! Team assignments — at least one team is required. Each entry grants the invitee membership in that team with the given role on acceptance.

KnowledgebaseChunkInput input

One chunk written into the knowledgebase table. Mirrors the columns exactly (minus `lexicalTsv`, which the DB trigger fills from `embeddedText`, and `createdAt` which uses the column default).

FieldTypeDescription
chunkIndex Int! 0-indexed position within the report.
pageStart Int! Inclusive PDF page span this chunk covers.
pageEnd Int!
chunkText String! Raw excerpt text — what a UI would render on a search hit.
contextPrefix String! LLM-generated context prefix (empty string when contextualization is skipped).
embeddedText String! `contextPrefix + "\n\n" + chunkText` — what was actually embedded and tokenised.
embeddingProvider String! Provenance for the embedding — mirrors the DB columns so a later re-embed backfill can filter by (provider, model).
embeddingModel String!
embedding [Float!]! 1024-dim dense embedding. The API validates length and casts to `vector(1024)` before insert.
locationIds [String!]! Resolved `locations.id` refs (post-lookup).
locationPcodes [String!]! Raw pcodes the LLM emitted but the resolver couldn't match.
timeRangeStart DateTime Optional event window described by the chunk.
timeRangeEnd DateTime
eventTypes [String!]! Multi-hazard tags — GLIDE codes or free-text categories.
needSectors [String!]! NRC SAF sectors: Shelter, WASH, Protection, Health, Food Security, Education.

KnowledgebaseFilters input

Optional filters applied BEFORE the retrieval step — array filters use overlap semantics (any-of), the time range uses inclusive intersection. Leave a field null to skip that filter.

FieldTypeDescription
locationIds [String!] Match rows tagged with ANY of these `locations.id` values.
eventTypes [String!] Match rows tagged with ANY of these event-type tags.
needSectors [String!] Match rows tagged with ANY of these SAF sectors.
timeRange DateRangeInput Match rows whose extracted event window overlaps this range.
currentEmbeddingModelOnly Boolean Restrict to rows written by the currently-configured embedding provider + model. Default true — mixing embedding spaces yields meaningless distances. Set false only when inspecting historical rows via BM25-only search (no vector step will be run for filtered-out rows).

LocaleTranslationInput input

Per-locale payload accepted by upsertTranslations. data mirrors the canonical entity's JSON shape exactly for the given locale — e.g. for a crisis it carries the localized title/summary/scenarios/needs with the same keys/nesting the English columns use.

FieldTypeDescription
locale String! BCP-47 lowercased — 'ar', 'fr'. 'en' is rejected (canonical).
data JSON! Translated payload. Shape mirrors the canonical entity per type.
sourceHashes JSON! Per-field SHA-256 hashes of the canonical English source used to produce `data`. Stored so the pipeline can detect which canonical field changed and re-translate only that field on the next pass.

ReplyToCommentInput input

FieldTypeDescription
repliedToCommentId String! ID of the comment to reply to.
comment String!
tagUserIds [String!] User IDs to tag in the reply.

SignalsPageInput input

FieldTypeDescription
limit Int
offset Int
orderBy SignalOrderBy
teamId String
locationId String
sourceNames [String!] Restrict to signals whose source name is in this list (e.g. ["acled","dataminr"]).
severityMin Int
severityMax Int
from DateTime Filter on signal.publishedAt — inclusive.
to DateTime
includeDummy Boolean

SubmitSignalLocationChallengeInput input

FieldTypeDescription
signalId String!
note String
proposedLng Float Omit both to file a bare challenge. Provide both (finite, in range) for a Location correction — one without the other is rejected.
proposedLat Float
proposedName String

SubscribeToAlertsBatchInput input

FieldTypeDescription
locationIds [String!]! One or more location IDs.
alertTypes [String!]! One or more disaster/event types (glideNumbers). A subscription is created for every (location × alertType) pair.
channel Channel!
frequency Frequency!
minSeverity Int Minimum event severity (1-5). Applied to all created subscriptions.

SubscribeToAlertsInput input

FieldTypeDescription
locationId String!
alertType String! Disaster/event type (glideNumber from disaster_types, e.g. 'fl', 'eq').
channel Channel!
frequency Frequency!
minSeverity Int Minimum event severity (1-5) to notify on. Defaults to 1 (all alerts).

TeamAssignmentInput input

One (team, role) assignment passed to inviteUser.

FieldTypeDescription
teamId String!
teamRole TeamMemberRole!

UpdateAlertInput input

FieldTypeDescription
status AlertStatus

UpdateAlertSubscriptionInput input

FieldTypeDescription
channel Channel
frequency Frequency
active Boolean
minSeverity Int Minimum event severity (1-5).

UpdateCrisisPopulationInput input

FieldTypeDescription
populationAffected String Population directly affected by the events (BigInt as string).
populationInArea String Total population residing within the event admin areas (BigInt as string).
title String AI-generated crisis title.
summary String AI-generated crisis summary.
scenarios JSON LLM-generated forward scenarios. Shape: { most_likely, best_case, worst_case, description }.

UpdateDataSourceInput input

FieldTypeDescription
name String
type String
isActive Boolean
baseUrl String
infoUrl String
synonyms [String!] Alias set for source-name normalisation (see resolveDataSource).
reliability Int NATO Admiralty-style reliability grade 1–4; null = ungraded.

UpdateEventInput input

FieldTypeDescription
signalIds [String!]
title String
description String
descriptionSignals JSON
validFrom String
validTo String
firstSignalCreatedAt String
lastSignalCreatedAt String
originId String
destinationId String
locationId String
types [String!]
severity Int
populationAffected String
populationDisplaced String Estimated population displaced (BigInt as string).
casualties Int Aggregated casualties for the event (max across constituent signals).
rank Float

UpdateGroundSourceInput input

Partial update of a source's policy record. Null/omitted fields are left unchanged; transportId is immutable (it is the identity that externalIds are minted against). The merged row is re-validated: group kinds must end up with a complete consent record.

FieldTypeDescription
name String
kind String "staff_group" | "partner_group" | "hotline".
consentScope String
consentRecordedAt String
consentRecordedBy String
privacyDefault String
reviewerRoles [String!]
retentionRule String

UpdateLocationInput input

FieldTypeDescription
geoId Int
osmId String
pCode String
name String
level Int
parentId String

UpdateOrganisationInput input

Fields for updating an existing organisation.

FieldTypeDescription
name String New display name.
slug String New URL-friendly identifier.
isActive Boolean Set active/inactive status.

UpdateProfileInput input

FieldTypeDescription
name String
phoneNumber String
image String
enableInAppNotification Boolean
enableEmailNotification Boolean
enableSMSNotification Boolean
language String Preferred UI language code (BCP-47 / ISO 639-1, e.g. "en", "ar").

UpdateTeamInput input

FieldTypeDescription
name String
slug String
description String

UpdateWebhookSubscriptionInput input

Input for updating an existing subscription. All fields are optional — provide only what you want to change.

FieldTypeDescription
name String
targetUrl String
eventTypeFilter [String!]
active Boolean

UpsertLocationMetadataInput input

FieldTypeDescription
locationId String!
type String! Type string (e.g. "iom_dtm_displacement").
data JSON! JSON payload for this type.

UpsertNominatimCacheInput input

FieldTypeDescription
queryHash String! SHA-256 hex digest of `<endpoint>:<normalised_query>`. The caller is responsible for computing this — the server doesn't re-hash.
query String! Raw query string for debugging / audit.
endpoint String! The Nominatim endpoint that produced this response.
responseJson JSON! Raw JSON response to cache. Stored verbatim.
status String! 'ok' for a usable result, 'no_result' for an empty result set, 'error' when the geocoder returned an error response. Negative results are cached so we don't re-ask the same dead question.
ttlSeconds Int! How long this entry should remain valid, in seconds. The server computes `expires_at = NOW() + ttl_seconds`.

UpsertReportDatapointsInput input

Input for `upsertReportDatapoints`. Field names / types mirror the ReportDatapoint output so a client can echo one back into the other with minimal transformation.

FieldTypeDescription
reportId String!
reportTitle String!
sourceUrl String!
publishedAt DateTime!
reportingPeriodStart DateTime
reportingPeriodEnd DateTime
locationIds [String!]!
locationPcodes [String!]!
eventTypes [String!]!
totalAffected Int
totalDisplaced Int
totalKilled Int
data JSON!
schemaVersion String!
extractedByModel String!
sourceId String The report's publisher source (a `data_sources` id), resolved by the pipeline via `resolveDataSource`. Null until source attribution backfills; a figure's own cited source lives per-figure inside `data`. See clear-context-pipeline ADR-0004.

UpsertSituationAnalysisInput input

FieldTypeDescription
countryLocationId String!
windowStart DateTime!
windowEnd DateTime!
windowKind String! Window granularity — `yearly` today. Required: it is part of the bucket key, so a wrong or missing value writes a row no reader will ask for. Not defaulted, deliberately.
data JSON!
sourceReportIds [String!]!
aggregatedDatapointId String
generatedByModel String!
generationCostUsd Float
schemaVersion String!

UpsertTranslationsInput input

FieldTypeDescription
entityType String! One of 'event' | 'crisis' | 'location' (case-insensitive).
entityId String!
translations [LocaleTranslationInput!]! One entry per target locale. Each row is upserted independently.

ActivityCountsByAction object

FieldTypeDescription
login Int!
signalCreateManual Int!
eventCreate Int!
alertCreate Int!
crisisCreate Int!
feedbackCreate Int!
total Int!

ActivityLog object

A single user-initiated action recorded for analytics.

FieldTypeDescription
id String!
user User!
action String! Namespaced action verb — e.g. 'auth.login', 'signal.create_manual', 'event.create', 'alert.create', 'crisis.create', 'feedback.create'.
resourceType String Coarse resource bucket — 'signal' / 'event' / 'alert' / 'crisis' / 'feedback' / 'session'.
resourceId String The created/affected row's id. Null for actions like login.
metadata JSON Per-action context (title, severity, etc.). Shape varies by action.
ipAddress String
userAgent String
createdAt DateTime!

ActivityStats object

Aggregate counts and time-series for the admin dashboard.

FieldTypeDescription
from DateTime! Window the stats cover, in ISO-8601. Mirrored back to the client so a paginated dashboard can know what range its numbers are based on.
to DateTime!
totals ActivityCountsByAction! Total event counts across the window, keyed by action.
byUser [UserActivitySummary!]! Per-user breakdown, ordered by total activity (desc), limited to the top 50 most-active users in the window.
byDay [DailyActivityCount!]! Daily activity counts across the window (UTC dates).

AggregatedDatapoint object

One aggregated bucket - the roll-up of every contributing `report_datapoint` for a scope (window × window_kind × location). Consumed by the situation-analysis dashboard tiles and by chatbot factual queries. See `AggregatedField` docstring for the JSON `data` shape.

FieldTypeDescription
id String!
windowStart DateTime!
windowEnd DateTime!
windowKind String! One of `weekly` | `monthly` | `yearly` | `all`.
locationId String Null when the bucket rolls up to a country (yearly, all-time tiers). Otherwise references `locations.id`.
data JSON! Flat map keyed by field label. Each value is either a QualityEnvelope (for numeric fields), a SetUnionEnvelope (for label fields - `{ values, contributing_report_ids }`), or `null` when no report in scope reported that field. A numeric QualityEnvelope carries `{ value, unit, confidence_mix, newest_report_at, oldest_report_at, contributing_report_ids }` plus the credibility fields (clear-context-pipeline ADR-0004/0005): the cached time-invariant `reliability` (1–4) and `intrinsic_credibility` (0–8.5), and - added on every read - `recency` (0–1.5), `information_credibility` (0–10), and `data_quality` (**0–10**), the per-field headline. The legacy `quality_score` (0–1, directness-only) is retained for backwards compatibility.
contributingReportIds [String!]!
newestSourceAt DateTime!
oldestSourceAt DateTime!
dataQualityScore Float! Bucket headline data quality on a **0–10** scale (clear-context-pipeline ADR-0005): the mean of the fields' read-time `data_quality` (`(reliability × 2.5 × information_credibility) / 10`, Recency folded in at read). The stored column carries the same 0–10 scale. NOTE: this is a scale change from the pre-data-quality `quality_score` (0–1) - thresholds tuned on the old scale must be re-tuned.
reportCount Int!
estimatedCurrentTotals CurrentTotals Estimated current totals - latest authoritative stock + the flows reported after it (ADR-0006 §4). This is an **as-of-now** figure, NOT the bucket's period: it is returned only for a bucket whose window still includes now (the current year/month/week and the `all` tier) and is `null` on any historical bucket, so a past-period row never carries a present-day number. Resolved lazily - the bounded `report_datapoints` scan runs only when this field is selected - but it is a real scan, not free.
validFrom DateTime! Bitemporal validity - this snapshot's lifetime as a "current" row. `validTo` is null when the row is still the current one for its bucket; else it carries the timestamp when a newer computation superseded it.
validTo DateTime
schemaVersion String!
computedAt DateTime!
onDemand Boolean! True when the resolver assembled this bucket on-demand from `report_datapoints` rather than serving it from the pre-compute cache. The dashboard can render a "just computed" indicator so users know the number reflects the freshest possible view.

Alert object

An alert created from an event, distributed to subscribed users.

FieldTypeDescription
id String!
event Event! The event this alert was created from.
representativePoint Location Location of the alert's event's FIRST signal — the same point as `event.representativePoint`, surfaced directly on the alert so a marker can be placed without walking into the event. Null when the first signal has no located point. Requires any authenticated content reader.
status AlertStatus!
createdAt DateTime! When the alert row was first created.
updatedAt DateTime! Last time the alert row was updated (e.g. status transition).
userAlerts [UserAlert!]! Users who received this alert.

AlertsPage object

FieldTypeDescription
items [Alert!]!
totalCount Int!
hasMore Boolean!

AlertSubscription object

A user's subscription to alerts of a specific type at a specific location.

FieldTypeDescription
id String!
userId String!
user User!
location Location!
alertType String! Disaster/event type to subscribe to (e.g. 'fl' for flood, 'eq' for earthquake).
active Boolean!
minSeverity Int! Minimum event severity (1-5) to notify on. Alerts with event.severity < minSeverity are suppressed for this user.
channel Channel!
frequency Frequency!
createdAt DateTime!
updatedAt DateTime!

ApiKey object

A personal API key for programmatic access. The full key is only shown once at creation.

FieldTypeDescription
id String!
name String! Descriptive name you chose when creating the key.
prefix String! Short prefix for identification (e.g. sk_live_abc1).
expiresAt DateTime Optional expiration date. Expired keys are rejected automatically.
lastUsedAt DateTime When this key was last used to authenticate a request.
revokedAt DateTime When this key was revoked, if applicable. Revocation is permanent.
createdAt DateTime!
updatedAt DateTime!

ApproveUserResult object

Result of the admin-only `approveUser` mutation. Flips a `pending` user's role to `viewer` and moves the matching CRM contact from the prospects collection into the approved collection, triggering Exponential's welcome automation.

FieldTypeDescription
user User! The approved user (with the new role).
crmMoved Boolean! True when the CRM-side list move completed cleanly. False means the user is approved locally but the CRM record still sits in the prospects list — the admin can retry from /portal/admin without re-approving the user.
crmWarnings [String!]! Non-fatal CRM warnings, if any (e.g. profileType update failed while the list swap succeeded). Empty on the happy path.

ArchiveStaleAlertsResult object

Result of the archiveStaleAlerts bulk mutation.

FieldTypeDescription
alertsArchived Int!

CommentTag object

A tag linking a user to a comment.

FieldTypeDescription
user User!
comment UserComment!

CreateApiKeyPayload object

Returned only from createApiKey. Contains the full plaintext key that will never be retrievable again.

FieldTypeDescription
apiKey ApiKey!
key String! The full API key. Copy this immediately — it cannot be retrieved later.

CreateDevUserResult object

Result of the admin-only `createDevUser` mutation. The plaintext API key is returned only here — it is never stored and cannot be retrieved later. The admin UI surfaces it once so the operator can copy-paste it in case the welcome email bounces.

FieldTypeDescription
user User! The newly-provisioned user.
plaintextKey String! The full plaintext API key (sk_live_…). Save this immediately — it will not be retrievable from the API after this response is read.
welcomeEmailSent Boolean! True when the welcome email was successfully handed off to the email provider.
setPasswordTokenIssued Boolean! True when a long-lived set-password verification token was successfully issued. The welcome email contains a link that consumes the token.

CreatePublicEventLinkResult object

Returned once from `createPublicEventLink`. The token is part of the URL and not retrievable again — same one-time-show semantics as `createApiKey`.

FieldTypeDescription
token String! The plaintext token. Embedded in `url` for convenience.
url String! Pre-built /public/event/<eventId>/<token> URL on the frontend. Hand this straight to the share UI.
expiresAt DateTime! Wall-clock expiry — when the cached snapshot will be dropped from Redis (assuming it isn't evicted earlier or revoked).

Crisis object

A crisis aggregates multiple events into a single coherent narrative.

FieldTypeDescription
id String!
title String
summary String
enrichmentStatus CrisisEnrichmentStatus! Enrichment status for the pipeline drain (PENDING until enrichment runs).
scenarios JSON Forward-looking scenarios generated by the LLM enrichment task. Shape: { most_likely, best_case, worst_case, description }. Null until the enrichment task runs.
severity Float! Severity score (aggregated from linked events).
generalLocation Location General location of the crisis.
needs JSON! Needs associated with the crisis (JSON structure).
populationAffected String Population directly affected by linked events (BigInt as string).
populationInArea String Total population residing within the admin areas of linked events (BigInt as string).
attachments [String!]! Supporting documents uploaded by users (reports, photos, PDFs). Returned as presigned URLs — the underlying storage is S3 keys, which the resolver converts at read time so links stay short-lived.
events [Event!]! Events that are part of this crisis.
feedbacks [UserFeedback!]! User feedback on this crisis.
comments [UserComment!]! User comments on this crisis.
createdAt DateTime! When the crisis row was created.
updatedAt DateTime! When the crisis was last written to — bumped automatically by Prisma on every update path (title / description / population / attachments / event-linkage / needs / scenarios). Useful for cache invalidation and "recently updated" surfaces.

CurrentTotals object

Estimated current totals for a country bucket (ADR-0006 §4). Each metric is `null` when there is no anchoring stock in scope to accrue flows onto.

FieldTypeDescription
displacement StockFlowEstimate IDP displacement: latest `idp_stock` + `new_displacements` since T₀.
returns StockFlowEstimate Returns: latest `returnee_stock` + `new_returns` since T₀.

DailyActivityCount object

FieldTypeDescription
date String! YYYY-MM-DD in UTC.
total Int!
counts ActivityCountsByAction!

DataSource object

An external data source that feeds signals into the system.

FieldTypeDescription
id String!
name String!
type String! Source type identifier (e.g. satellite, sensor, manual).
isActive Boolean!
baseUrl String Base URL of the data source API.
infoUrl String URL with more information about this source.
synonyms [String!]! Alias set for source-name normalisation — variants that resolve to this same source (e.g. "IOM DTM" / "Displacement Tracking Matrix" / "DTM").
reliability Int NATO Admiralty-style source-reliability grade, 1 (least) – 4 (most). Null = ungraded; the data-quality formula treats null as 1. See clear-context-pipeline ADR-0004.
createdAt DateTime!
updatedAt DateTime!
signals [Signal!]! Signals collected from this data source.

DauPoint object

One day in a DAU time series.

FieldTypeDescription
date String! ISO date (YYYY-MM-DD, UTC).
uniqueUsers Int! Number of distinct users who logged in on this date.

DisasterLevel1 object

A level-1 category with its level-2 groups.

FieldTypeDescription
name String!
groups [DisasterLevel2!]!

DisasterLevel2 object

A level-2 group with its distinct classification codes.

FieldTypeDescription
name String!
codes [String!]! Classification codes belonging to this level-2 group (usually one).
subTypes [DisasterType!]! Level-3 sub-types under this level-2 group.

DisasterType object

A disaster classification in a 3-level hierarchy (level1 > level2 > level3). Events store arrays of codes (GLIDE or CLEAR IDs).

FieldTypeDescription
id String!
disasterType String! Level-3 granular sub-type (e.g. "peaceful protest").
disasterClass String! Level-1 category (legacy — same as level1).
glideNumber String! Classification code — may be a GLIDE number or a CLEAR id.
level1 String! Top-level category (e.g. "natural hazard", "conflict").
level2 String! Mid-level group (e.g. "protests", "battles").
idType String! Identifier system: "glide_number" or "clear_id".

EntityStats object

FieldTypeDescription
total Int! Total matching the filter, regardless of groupBy.
buckets [StatsBucket!]! Buckets when groupBy != none. Empty when groupBy = none.

Event object

An event grouping related signals into a coherent narrative.

FieldTypeDescription
id String!
title String
description String
descriptionSignals JSON LLM-generated signal descriptions as JSON.
validFrom DateTime!
validTo DateTime!
firstSignalCreatedAt DateTime!
lastSignalCreatedAt DateTime!
originLocation Location Origin location of the event.
destinationLocation Location Destination location of the event.
generalLocation Location General location (when no origin/destination).
representativePoint Location Location of the event's FIRST signal (the one recorded in `firstSignalCreatedAt`) — a single point ready to drop as a map marker, so a client doesn't have to fetch every signal and pick one. The first non-null of the signal's origin → destination → general location; null when the first signal has no located point. Requires any authenticated content reader.
types [String!]! Event type tags.
severity Int Severity score (1–5). Aggregated from signal severities.
isDummy Boolean! Whether this is seed/demo data.
populationAffected String Estimated population affected.
populationDisplaced String Estimated population displaced by the event (BigInt as string).
casualties Int Aggregated casualties for the event (max across constituent signals).
rank Float!
signals [Signal!]! Signals linked to this event.
alerts [Alert!]! Alerts created from this event.
feedbacks [UserFeedback!]! User feedback on this event.
comments [UserComment!]! User comments on this event.
escalations [EventEscalation!]! Escalations by users.

EventCrisis object

Link between a crisis and an event.

FieldTypeDescription
id String!
crisisId String!
eventId String!
collectedAt DateTime!
crisis Crisis!
event Event!

EventEscalation object

Tracks a user escalating an event, optionally to a crisis.

FieldTypeDescription
id String!
user User!
event Event!
isCrisis Boolean! Whether this has been escalated to a crisis.
validFrom DateTime!
validTo DateTime!

EventsPage object

FieldTypeDescription
items [Event!]!
totalCount Int!
hasMore Boolean!

FeatureFlag object

A feature toggle that controls runtime behavior.

FieldTypeDescription
id Int!
key String! Unique key used to look up this flag (e.g. dark_mode).
enabled Boolean!
updatedAt DateTime!

FindOrCreateLandmarkL4Result object

Outcome of findOrCreateLandmarkL4.

FieldTypeDescription
locationId String The resolved L4 id. Null when the same-A2 safety check aborts.
reused Boolean! True when the call resolved to an existing L4, false when a new one was created. Meaningless when locationId is null.
pointType String Provenance tag of the resolved L4 ('landmark-geocoded', 'gps', etc). Null when the call aborted without producing a row.
abortedReason String Set to 'different_a2' when the candidate and source coords resolve to different containing A2s. Null on success.

GazetteerHit object

A GeoNames gazetteer match for a place name. Coordinates come straight from GeoNames — callers still validate them (e.g. the same-A2 check against a signal's source coordinates) before trusting a fuzzy hit.

FieldTypeDescription
geonamesId Int!
name String!
latitude Float!
longitude Float!
featureClass String GeoNames feature class: A=admin area, P=populated place, H=hydro, L=area, S=spot/POI, T=terrain, … The resolver prefers P/A.
featureCode String
countryCode String! ISO-3166-1 alpha-2 (SD, VE, AF).
population Float!
score Float! Match confidence 0–1: 1.0 for an exact normalised-name hit, else the pg_trgm similarity of the best fuzzy match.
exact Boolean! True when matched exactly on the normalised name, false when fuzzy.

GroundIngestResult object

Result of a chat-export ingest (also returned by the REST upload route as JSON).

FieldTypeDescription
created Int!
skipped Int!
mediaStored Int!
mediaUnmatched [String!]!

GroundMessage object

A staged Signal: a canonical message parsed from a WhatsApp source, held in the ground staging tier until its thread is reviewed. Text is phone-number-redacted at persistence. senderName is private-tier data and is scrubbed from anything promoted to the signals graph.

FieldTypeDescription
id ID!
groundSourceId String!
externalId String! Idempotency id, scheme "whatsapp:{groupJid}:{messageId}".
sentAt DateTime!
senderRef String! Pseudonymous per-(source, sender) reference, e.g. "s_ab12cd34ef56".
senderName String Raw sender display name. Private tier only — never promoted.
text String! Message text (redacted). Empty for caption-less media messages.
mediaKeys [String!]! S3 keys of stored attachments.
mediaUrls [String!]! Presigned GET URLs for mediaKeys (1 h expiry), generated at read time — URLs are never stored.
mediaRefs [String!]! Attachment filenames referenced by the export.
omittedMediaCount Int! Media the export omitted ("image omitted") — the message still counts as a media message.
classification String "field_report" | "news_digest" | "operational" | "chatter"; null until the pipeline classification task labels the message.
uncertainty String Contributor's own uncertainty tag ("unconfirmed", "rumour"), preserved from the source text.
isEdited Boolean!
threadId String
createdAt DateTime!

GroundMessageForClassification object

Pipeline-facing projection of a staged Signal for the classification/threading worker (clear-pipeline's classify_ground_messages task). Deliberately excludes senderName — the pipeline never sees private-tier identity, only the pseudonymous senderRef.

FieldTypeDescription
id ID!
text String!
sentAt DateTime!
senderRef String!
hasMedia Boolean! True when the message carries stored media, export-referenced attachments, or export-omitted media.
classification String Current label, null while unclassified.
threadId String Current thread (placeholder or pipeline-built).

GroundSource object

Per-source policy record for ground intelligence (WhatsApp groups, future hotline). Carries the consent scope, privacy default, reviewer roles, and retention rule for everything ingested from the source. The whole ground staging tier is private: admin/analyst only.

FieldTypeDescription
id ID!
name String!
kind String! Source kind: "staff_group" | "partner_group" | "hotline".
transportId String! Transport binding — WhatsApp group JID, or hotline number.
consentScope String What the source's members consented to (free text, e.g. "links and resources only"). Ingest policy is judged against this record.
consentRecordedAt DateTime
consentRecordedBy String Who recorded/gave the consent (person name/role, not a user id).
privacyDefault String! Review default for derived threads. V1: always "private".
reviewerRoles [String!]! Global roles allowed to review threads from this source.
retentionRule String Free-text retention rule; enforcement is operational in V1.
isActive Boolean!
createdAt DateTime!
updatedAt DateTime!

GroundThread object

A thread — a cluster of staged Signals — in the review queue. V1 threads are one-per-message placeholders until the pipeline threading task clusters them. Lifecycle state models the correction chain; review state is the human gate in front of the signals graph. An approved thread is promoted and becomes a Signal.

FieldTypeDescription
id ID!
groundSourceId String!
source GroundSource!
title String
lifecycleState String! "reported" | "updated" | "confirmed" | "corrected" | "retracted".
reviewState String! "unverified" | "approved_private" | "approved_public" | "rejected".
reviewedBy String Auth user id of the reviewer who last transitioned reviewState.
reviewedAt DateTime
reviewNote String
promotedSignalId String Id of the `signals` row created when this thread was promoted (approved_public only).
messages [GroundMessage!]!
messageIds [String!]! Ids of the thread's messages, oldest first. The pipeline worker selects this (via groundThreadsForSource) instead of `messages` — it carries no message content and no sender identity.
createdAt DateTime!
updatedAt DateTime!

Invitation object

An invitation to join an organisation, with one or more team assignments.

FieldTypeDescription
id String!
email String!
organisation Organisation!
team Team Single team — legacy field, populated only for invitations created before the multi-team join existed. New invitations use `teams`.
role String! Organisation role assigned on acceptance.
teamRole String Legacy single-team role. Use `teams` for new invitations.
teams [InvitationTeam!]! Team assignments granted to the invitee on acceptance. Empty list means org-only access (no team).
expiresAt DateTime!
acceptedAt DateTime
invitedBy User!
createdAt DateTime!
status InvitationStatus! Computed from acceptedAt and expiresAt.

InvitationInfo object

Public invitation info returned by token lookup (limited fields).

FieldTypeDescription
id String!
email String!
organisationName String!
teamName String Legacy single-team name (populated only for old invitations).
role String!
teamRole String Legacy single-team role (populated only for old invitations).
teams [InvitationInfoTeam!]! Team assignments the invitee will be granted on acceptance.
expiresAt DateTime!
status InvitationStatus!

InvitationInfoTeam object

One (team, role) assignment as returned by the public token lookup.

FieldTypeDescription
teamId String!
teamName String!
teamRole TeamMemberRole!

InvitationTeam object

One (team, role) assignment attached to an invitation.

FieldTypeDescription
team Team!
teamRole TeamMemberRole!

KnowledgebaseHit object

One hit from `searchKnowledgebase`. Carries enough source provenance (report title, url, page range) that a UI can render a citation without a follow-up fetch, plus the parameter arrays so the caller can highlight which filters matched.

FieldTypeDescription
id String!
reportId String!
reportTitle String!
sourceUrl String!
publishedAt DateTime
pageStart Int!
pageEnd Int!
chunkText String!
score Float! RRF-fused score. Larger is better. Not directly comparable across queries — the constant `k=60` bounds each per-row term to 1/(k+1)=~0.016, so a two-source hit tops out near 0.032.
locationIds [String!]!
eventTypes [String!]!
needSectors [String!]!

KnowledgebaseIngestJob object

One Dagster run kicked off by `uploadKnowledgebaseDocument`. Returned by both the mutation (initial launch) and the polling query. `reportId` / `reportTitle` / `s3Key` are populated from the run's Dagster tags, which the mutation attaches at launch time — a client that lost track of the initial payload can still recover the identity of the document being ingested.

FieldTypeDescription
runId String! Opaque Dagster run id. Pass to `knowledgebaseIngestJob` to poll for completion.
status KnowledgebaseIngestStatus!
reportId String The knowledgebase report_id the ingest is targeting. Same value surfaces on `Knowledgebase.reportId` once the run succeeds.
reportTitle String
s3Key String
startedAt DateTime
endedAt DateTime

Location object

A geographic location in a hierarchy (country > state > city, etc.).

FieldTypeDescription
id String!
geoId Int GeoNames identifier.
osmId String OpenStreetMap identifier.
pCode String P-Code identifier.
name String!
level Int! Hierarchy level: 0 = country, 1 = state/province, 2 = city, etc.
geometry GeoJSON Geometry as GeoJSON (Point or MultiPolygon).
parent Location Parent location in the hierarchy.
children [Location!]! Child locations one level below.
ancestorIds [String!]! IDs of all ancestor locations (parent, grandparent, etc.).
ancestors [Location!]! All ancestor locations (parent, grandparent, etc.).
population String Population residing within this location's geometry. Null for point locations or when not yet computed.
pointType String Provenance of this location's geometry. Notable values: - 'landmark-geocoded': L4 created by the pipeline geoparser from a landmark/place name resolved via Nominatim. The location's `name` field is meaningful for display (e.g., 'Nyala Airport'). - 'gps' / 'source-gps' / 'centroid' / 'source-centroid': see schema documentation on the Prisma model. - null: legacy rows (most signal-title L4s from `createPointLocation`).
metadata [LocationMetadata!]! Per-type metadata (IOM DTM, INFORM, etc). Pass a type argument to filter. By default only the current value is returned (validTo is null). Pass current: false to include the full history.
type: Stringcurrent: Boolean
createdAt DateTime! When the row was first created.
updatedAt DateTime! Last write — useful for tracking admin polygon (re-)loads.

LocationMetadata object

Arbitrary per-location metadata keyed by type. History is preserved: writes close the current row (set validTo = now()) and insert a new one. The CURRENT value for a (location, type) is the row where validTo is null.

FieldTypeDescription
id String!
location Location!
type String! Free-form type string (e.g. "iom_dtm_displacement", "acaps_inform").
data JSON! Source-specific payload stored as JSON.
validFrom DateTime! When this value became current.
validTo DateTime When this value was superseded. Null means still current.
createdAt DateTime!
updatedAt DateTime!

MauPoint object

One month in a MAU time series.

FieldTypeDescription
month String! ISO month (YYYY-MM, UTC).
uniqueUsers Int! Number of distinct users who logged in at least once during the calendar month.

NominatimCacheEntry object

A cached Nominatim-protocol geocoder response.

FieldTypeDescription
id String!
queryHash String! SHA-256 hex digest of `<endpoint>:<normalised_query>`.
query String! Raw query string (for debugging only).
endpoint String! The Nominatim endpoint that produced this response (e.g. 'search', 'reverse').
responseJson JSON! Raw JSON response from the geocoder.
status String! Lookup outcome: 'ok' / 'no_result' / 'error'.
fetchedAt DateTime!
expiresAt DateTime!

Notification object

FieldTypeDescription
id String!
user User!
message String!
notificationType String!
actionUrl String
actionText String
status NotificationStatus!
emailNotificationStatus NotificationStatus
smsNotificationStatus NotificationStatus
createdAt DateTime!
updatedAt DateTime!

Organisation object

An organisation that owns teams and has members.

FieldTypeDescription
id String!
name String!
slug String! URL-friendly identifier.
isActive Boolean! Whether this organisation is active. Inactive organisations are hidden from non-admin users.
createdAt DateTime! When this organisation was created.
updatedAt DateTime! When this organisation was last updated.
teams [Team!]! Teams belonging to this organisation.
members [OrgMember!]! Members of this organisation.

OrganisationUser object

Links a user to an organisation with a role.

FieldTypeDescription
id String!
userId String!
organisationId String!
role String!

OrgMember object

Links a user to an organisation with an org-level role.

FieldTypeDescription
id String!
user User!
role String! Organisation-level role: org_admin or member.
createdAt DateTime! When this membership was created.

PipelineCountry object

A country the CLEAR pipeline publishes a Situation Analysis for, with the bounding box used to create its level-0 Country location. bbox order matches ensureCountryLocation: [minLng, minLat, maxLng, maxLat].

FieldTypeDescription
name String!
iso3 String! ISO 3166-1 alpha-3 code (e.g. "SDN"). Used by pipeline ingests that scope external APIs by country (HAPI location_code, IOM DTM Admin0Pcode).
pcode String! The admin-0 location's pcode (ISO 3166-1 alpha-2, e.g. "SD"). The strong, name-independent key for resolving the country's level-0 location — the stored name is often the long official form, which exact-name lookup misses.
bbox [Float!]!

PublicEvent object

The slim, unauthenticated view of an event used for share-link rendering. Deliberately a new type — not a slim wrapper around `Event` — so the field set is auditable in one place and field- selection can't accidentally leak signals, comments, alerts, or any other gated data through the public surface. Returned by the `publicEvent(eventId, token)` query when the Redis-cached snapshot for that token is still alive. Cache TTL defaults to 30 days; entries can be evicted earlier under memory pressure, in which case the link expires and the user has to request a new one.

FieldTypeDescription
id String!
title String Localised event title (resolved to the canonical English text when the snapshot was taken — the public page is not locale-aware).
description String Localised event description (canonical English at snapshot time).
severity Float Severity score (1–5) at snapshot time.
validFrom DateTime! Window during which the event is considered valid.
validTo DateTime!
types [String!]! Disaster type codes (e.g. `fl`, `pa`). The frontend resolves these to display names via its own disaster-type catalogue.
primaryLocationName String Human-readable name of the event's primary location (general → origin → destination, first non-null).
primaryLocationCoords [Float!] `[lng, lat]` of the primary location's centroid when that location has a Point geometry. Null for polygon-only locations — the frontend hides the minimap in that case.
populationAffected String Stringified bigint (matches the authenticated `Event` field shape). Null when unknown.
populationDisplaced String
signalPoints [PublicEventSignalPoint!]! `[lng, lat]` for every Point location attached to the event's constituent signals (origin / destination / general fields, deduped by location id). Polygon-only signal locations are omitted. Empty when no signals carry Point geometry — the frontend hides the markers list in that case.
locationPopulation String Population of the event's administrative area, resolved by walking the primary location's ancestor chain in A2 → A1 → A0 order (district → state → country) and picking the deepest non- null. Mirrors the fallback the in-app event page uses so the share card shows the same number. Stringified bigint; null when no level along the chain has a population. See also `locationPopulationLevel` and `locationPopulationName`.
locationPopulationLevel Int Level the population number came from: `2` (district), `1` (state), `0` (country). Null when `locationPopulation` is null.
locationPopulationName String Name of the location the population number is for (e.g. "South Darfur"). Null when `locationPopulation` is null. Lets the frontend label the figure with context — "Population of South Darfur" reads more honestly than a bare number when we fell back to a country-level figure.
locationIdp String Internally-displaced persons count for the event's admin area, sourced from `locationMetadata(type = "iom_dtm_displacement")`. Same A2 → A1 → A0 fallback as `locationPopulation`. Stringified bigint; null when no displacement metadata exists for any level.
locationIdpLevel Int Level the IDP number came from. Null when `locationIdp` is null.
locationIdpName String Name of the location the IDP figure is for. Null when `locationIdp` is null.
sharedAt DateTime! When the share link was minted.
expiresAt DateTime! Soft expiry — the Redis TTL the cached snapshot was written with. The frontend uses this to render "Link expires in N days".

PublicEventSignalPoint object

One Point location attached to one of the event's signals.

FieldTypeDescription
name String Display name of the location (e.g. "Al Fasher"). Often null for raw point-locations like "Point 15.62, 30.21".
lng Float! Geographic longitude.
lat Float! Geographic latitude.

RefreshAggregatedDatapointsResult object

Summary counts from a `refreshAggregatedDatapoints` run.

FieldTypeDescription
computedBuckets Int!
supersededBuckets Int!
situationAnalysesInvalidated Int! Count of `situation_analyses` current rows that had their `validTo` stamped as a cascade of the yearly-country aggregation writes in this run. Zero means either no yearly buckets were touched or no situation-analysis snapshot existed yet for the affected countries.
schemaVersion String!

ReportDatapoint object

One report's extracted structured datapoints. Keys inside `data` are the six domain names: `timing_and_scope`, `casualties`, `displacement`, `needs_and_funding`, `access_and_incidents`, `narrative_and_confidence`. A domain whose extraction failed is written as `null` - the operator can re-run a targeted extraction on that domain later without touching the successful ones.

FieldTypeDescription
id String!
reportId String!
reportTitle String!
sourceUrl String!
publishedAt DateTime!
reportingPeriodStart DateTime Window the report CONTENT describes. Not the publication date.
reportingPeriodEnd DateTime
locationIds [String!]! Resolved `locations.id` values, deduped.
locationPcodes [String!]! Raw pcodes the LLM emitted that the resolver couldn't tie to a CLEAR location. A nightly backfill re-attempts these.
eventTypes [String!]! Free-text event tags (`conflict`, `flood`, `displacement`, `disease-outbreak`, …). Multi-hazard reports carry multiple.
totalAffected Int Denormalised hot totals - cheap dashboard filter/sort keys. NULL when the report doesn't headline the figure; DO NOT sum these across rows (the JSON blob carries the incident-safe form).
totalDisplaced Int
totalKilled Int
data JSON! Exhaustive per-report payload. See the Pydantic sub-schemas in `datapoints_schemas.py` for shape.
schemaVersion String! Extraction schema version, e.g. `v1`. Rows with different versions must not be aggregated together.
extractedByModel String! LLM identifier that produced the extraction (e.g. `claude-sonnet-4-6`). Used by the reviewer-audit workflow.
extractedAt DateTime!
sourceId String The report's publisher (a `data_sources` id), resolved by the pipeline via `resolveDataSource`. This is the report-level fallback; a figure's own cited origin lives per-figure inside `data`. See clear-context-pipeline ADR-0004.
source DataSource The resolved publisher, e.g. "IOM DTM" or "WFP". Exposed alongside `sourceId` so a client can label a report without a second round trip. **Null for rows extracted before source attribution landed (schema v1).** Those were never re-extracted, so clients must keep a fallback - deriving a publisher from the `sourceUrl` host yields the aggregator ("reliefweb.int"), not the publisher, so it is a label of last resort.

RotateDevUserApiKeyResult object

Result of the admin-only `rotateDevUserApiKey` mutation.

FieldTypeDescription
user User! The user whose key was rotated.
plaintextKey String! The fresh plaintext API key — same one-time-delivery rules as createDevUser.
notificationEmailSent Boolean! True when the rotation-notice email was successfully handed off.

Signal object

A signal derived from a data source.

FieldTypeDescription
id String!
source DataSource! The data source this signal was collected from.
status SignalStatus! Processing status for the pipeline drain (NEW until downstream runs).
processedAt DateTime When the downstream pipeline finished processing this signal (null while NEW).
rawS3Key String Pointer to the raw payload blob in the S3 data lake, when landed there.
externalId String Stable upstream identifier (e.g. "dataminr:{alertId}"). Used to deduplicate ingestion — (source, externalId) is unique.
publishedAt DateTime!
collectedAt DateTime!
url String
title String
description String
severity Int Severity score (1–5). From data source or estimated by pipeline.
casualties Int Reported casualties for the signal. Sourced from ACLED's fatalities field; for Dataminr, parsed from raw text via regex.
media [String!]! Media URLs (S3 keys for manual uploads, or source URLs for pipeline signals).
isDummy Boolean! Whether this is seed/demo data.
originLocation Location Origin location of the signal.
destinationLocation Location Destination location of the signal.
generalLocation Location General location (when no origin/destination).
events [Event!]! Events this signal is linked to.
feedbacks [UserFeedback!]! User feedback on this signal.
comments [UserComment!]! User comments on this signal.
locationChallenge SignalLocationChallenge Open Location challenge for this signal, if any (null when none).

SignalLocationChallenge object

An analyst's Location challenge on a Signal, with an optional Location correction (Location trust v1). Queue only — status is always "consideration", there is no accept/reject, and it never mutates the Signal's own geometry.

FieldTypeDescription
id ID!
signalId String!
status String! v1 is always "consideration".
note String Optional note explaining why the pin looks wrong.
proposedLng Float Proposed corrected point. Both lng and lat are set together, or both null (a bare challenge with no correction).
proposedLat Float
proposedName String Optional label for the proposed point.
createdBy String! Auth user id of whoever filed the challenge.
createdAt DateTime!
updatedAt DateTime!
hasProposedPoint Boolean! True when proposedLng/proposedLat are both set.

SignalsPage object

FieldTypeDescription
items [Signal!]!
totalCount Int!
hasMore Boolean!

SituationAnalysis object

One (country × year) situation-analysis snapshot. Cache read path — always current unless `asOf` is supplied for historical reads.

FieldTypeDescription
id String!
countryLocationId String! FK to `locations.id` — the country (A0) this analysis covers.
windowStart DateTime!
windowEnd DateTime!
windowKind String! Granularity of the analysis window — `yearly` today. Part of the bucket key alongside `countryLocationId` and `windowStart`; `windowEnd` is a derived detail and is never matched on.
data JSON! Full analysis blob. Top-level keys: `datapoints`, `ai_summary`, `context_risks`, `hazards_and_vulnerabilities`, `displacement`, `sectors`, `sources`. LLM-generated components carry their own `source_report_ids` for per-component provenance.
sourceReportIds [String!]! Denormalised union of every contributing report id across all components. Same identity as the `report_id` on the underlying `knowledgebase` / `report_datapoints` rows.
aggregatedDatapointId String Aggregated-datapoints snapshot this analysis was generated against, when known. Null for early bootstrap runs.
generatedByModel String! LLM identifier (e.g. `claude-sonnet-4-6`). Deterministic-only Phase B runs record `deterministic:<version>`.
generationCostUsd Float Total LLM cost across all component calls. Null when the generator didn't track cost or the run was deterministic-only.
generatedAt DateTime!
validFrom DateTime! Bitemporal validity — this snapshot's lifetime as a "current" row. `validTo` is null when the row is still the current one for its bucket; else it carries the timestamp when a newer regeneration superseded it.
validTo DateTime
schemaVersion String! Bumped when the output taxonomy OR the prompt set changes. Different-version rows never mix on trend views.

StatsBucket object

FieldTypeDescription
key String! Bucket label — depends on groupBy: glide code, severity number as string, or ISO date/week/month.
count Int!

StockFlowEstimate object

One metric's estimated current total (ADR-0006 §4): the latest authoritative stock plus the flows reported after its reference date T₀. Flows at or before T₀ are already embedded in the stock and are not added again - the invariant that stops returnee/IDP totals from over-counting.

FieldTypeDescription
total Float! `stock` + `flowsSince` - the headline estimated current total.
stock Float! The anchoring latest authoritative stock value (API-reconciled).
flowsSince Float! Deduped sum of the flows whose as-of date is strictly after T₀.
t0 DateTime! T₀ - the anchor stock's reference date. Flows at/before it are treated as already counted inside `stock`.
flowCount Int! Reports contributing forward flows - a provenance count, not a count of distinct flow events.

Team object

A team within an organisation, scoped to specific locations.

FieldTypeDescription
id String!
name String!
slug String! URL-friendly identifier, unique within the organisation.
description String
organisation Organisation! The organisation this team belongs to.
members [TeamMember!]! Members of this team.
locations [Location!]! Locations this team is scoped to. Empty means global monitoring.
createdAt DateTime!
updatedAt DateTime!

TeamMember object

Links a user to a team with a team-level role.

FieldTypeDescription
id String!
user User!
role String! Team-level role: lead, analyst, or viewer.
createdAt DateTime!

TranslationCoverage object

Per-(entity_type, locale) coverage snapshot used by the admin dashboard to see how much of the catalog has been translated. - canonicalCount: total entities of this type that *could* be translated (i.e. exist in the canonical table). - translatedCount: number with a row in the translations sidecar for this locale. The fraction translatedCount / canonicalCount is the live coverage for that (type, locale) cell — anything < 1.0 means the next read of one of the missing entities by a user on that locale will fall back to canonical English (and trigger the lazy-on-read enqueue, see utils/translation-loader.ts).

FieldTypeDescription
entityType String!
locale String!
canonicalCount Int!
translatedCount Int!

TranslationQueueItem object

A pending (re)translation request — the durable replacement for the lazy-on-read Celery enqueue. Drained by the Dagster translation consumer, which translates the entity at `locale` and writes it back via upsertTranslations (which clears the queue row).

FieldTypeDescription
id String!
entityType String! 'event' | 'crisis' | 'location'.
entityId String!
locale String! BCP-47 locale (lowercased) to translate into.
enqueuedAt DateTime!

TranslationRow object

A single translation row, returned by the admin/pipeline-only translations(entityType, entityId) query so the pipeline can compare stored source-hashes against the canonical row and decide which fields (if any) need re-translating.

FieldTypeDescription
locale String!
data JSON! Translated payload, same shape as the canonical entity per type.
sourceHashes JSON Per-field SHA-256 hashes of the canonical English source that was used to produce `data`. Null on rows written before hashes were introduced — treat null as "all fields stale".
createdAt DateTime!
updatedAt DateTime!

UpsertKnowledgebaseResult object

Result of a knowledgebase upsert — summary counts for logging.

FieldTypeDescription
reportId String!
chunksDeleted Int!
chunksInserted Int!

UpsertReportDatapointsResult object

Result of `upsertReportDatapoints` - summary for logs.

FieldTypeDescription
reportId String!
schemaVersion String!
createdOrReplaced Boolean! `true` when the mutation replaced a previously extracted row, `false` when it created the first row for the report.

UpsertSituationAnalysisResult object

Summary of an `upsertSituationAnalysis` mutation — reports whether the write superseded a previously-current row.

FieldTypeDescription
situationAnalysisId String!
countryLocationId String!
supersededPrevious Boolean! `true` when the mutation stamped `validTo` on a previous current row for the same bucket, `false` when this was the first row for the (country, window) tuple.

UpsertTranslationsResult object

Result of upserting one or more locale rows for a given entity. Carries the locales that were written so the caller (typically clear-pipeline) can confirm coverage without a follow-up read.

FieldTypeDescription
entityType String!
entityId String!
locales [String!]!

User object

A registered user with role-based access. PII fields (`email`, `phoneNumber`, `role`, `isActive`) and the private relation set (`alerts`, `notifications`, `organisations`, `teamMemberships`, `feedbacks`, `comments`, `escalations`) are gated server-side: - PII: returned when caller IS the user, is a global admin, OR shares at least one organisation with the user. Otherwise null (PII) or empty array (private relations). - Private relations: returned only when caller IS the user OR is a global admin. Sharing an organisation does NOT grant access to another member's inbox / comment history. Schema makes these fields nullable so the gate can return null when the caller is not authorised, instead of throwing — a logged-in analyst browsing an event's comments shouldn't see a hard error on the commenter's email.

FieldTypeDescription
id String!
email String
name String!
emailVerified Boolean!
phoneNumber String
image String
role String User role: viewer, editor, or admin. Null when caller is not authorised to see this user's role.
isActive Boolean Null when caller is not authorised to see this field.
language String! Preferred UI language code (BCP-47 / ISO 639-1, e.g. "en", "ar"). Defaults to "en".
enableInAppNotification Boolean!
enableEmailNotification Boolean!
enableSMSNotification Boolean!
createdAt DateTime!
updatedAt DateTime!
alerts [UserAlert!]! Alerts received by this user. Empty for non-self / non-admin callers.
notifications [Notification!]! Empty for non-self / non-admin callers.
defaultTeam Team The user's default team (last selected). Null for non-self / non-admin callers.
organisations [OrganisationUser!]! Empty for non-self / non-admin callers.
teamMemberships [TeamMember!]! Empty for non-self / non-admin callers.
feedbacks [UserFeedback!]! Empty for non-self / non-admin callers.
comments [UserComment!]! Empty for non-self / non-admin callers.
escalations [EventEscalation!]! Empty for non-self / non-admin callers.

UserActivitySummary object

FieldTypeDescription
user User!
total Int!
counts ActivityCountsByAction!

UserAlert object

Tracks an alert delivered to a user — view status.

FieldTypeDescription
id String!
user User!
alert Alert!
viewedAt DateTime When the user viewed this alert.

UserComment object

A user comment on a signal, event, or crisis, with reply support.

FieldTypeDescription
id String!
user User!
event Event
signal Signal
crisis Crisis
comment String!
isCommentReply Boolean! Whether this comment is a reply to another comment.
repliedToCommentId String ID of the comment being replied to, if any.
tags [CommentTag!]! Users tagged in this comment.
createdAt DateTime!
updatedAt DateTime!

UserEngagementMetrics object

Point-in-time engagement summary derived from auth.login activity. All three windows are trailing from `asOf` (inclusive).

FieldTypeDescription
asOf DateTime! Reference moment the windows are anchored to. Defaults to NOW() when the caller doesn't specify it.
dau Int! Unique users with at least one login in the trailing 24 hours.
wau Int! Unique users with at least one login in the trailing 7 days.
mau Int! Unique users with at least one login in the trailing 30 days.
dauMauRatio Float! Stickiness ratio (DAU / MAU) as a percentage. Industry benchmark: ≥20% indicates a frequently-returning user base. Returns 0 when MAU is 0 so the field is safe to chart without null-handling.

UserFeedback object

User feedback on a signal, event, or crisis — rating and optional text.

FieldTypeDescription
id String!
user User!
event Event
signal Signal
crisis Crisis
rating Int! Rating from 1 to 5.
text String Optional textual feedback.
createdAt DateTime!
updatedAt DateTime!

WebhookDelivery object

A single delivery attempt-sequence to one subscription for one incoming event. attemptNumber advances in place across retries.

FieldTypeDescription
id String!
subscriptionId String!
eventId String! GlitchTip's event/issue ID. Useful for correlating a delivery back to the source issue in GlitchTip.
eventType String! Event type extracted from the source payload (e.g. issue.new).
attemptNumber Int! 1-indexed. 1 = first attempt; up to 5 for retries.
responseStatus Int Last response HTTP status code, if the target responded.
responseBody String Truncated response body (max 8 KiB).
error String Network or transport error message, if the request never got a response.
succeededAt DateTime Non-null once a 2xx was received. Terminal state.
nextRetryAt DateTime Scheduled time of the next retry. Null when there is no next attempt (either succeeded, or exhausted 5 attempts = dead-lettered).
createdAt DateTime!
status WebhookDeliveryStatus! One of: pending, succeeded, retrying, dead. Derived from the fields above so the admin UI doesn't need to reimplement the logic.

WebhookSubscription object

Outbound webhook subscription. Every incoming GlitchTip alert is fanned out to every active subscription whose event-type filter matches. Managed by platform admins.

FieldTypeDescription
id String!
name String! Human-readable label shown in the admin UI. Not sent downstream.
targetUrl String! Target URL that receives HMAC-signed POST requests.
secret String HMAC-SHA256 shared secret used to sign outbound requests. Only returned on create + rotate; null on all other reads to keep the secret out of admin browser DevTools tabs. Rotate via rotateWebhookSubscriptionSecret.
active Boolean! Paused subscriptions are skipped during fan-out. Deliveries do NOT resume automatically when re-enabled — only new events fan out.
eventTypeFilter [String!]! Event-type allowlist. Empty = fire on all events. Values match GlitchTip's 'alias' field (e.g. issue.new, issue.regression, issue.resolved).
createdBy String User ID that created this subscription. Kept for audit.
createdAt DateTime!
updatedAt DateTime!
recentDeliveries [WebhookDelivery!]! Most-recent delivery attempts for this subscription. Ordered newest first. Capped at 50 in the resolver to keep the admin UI fast.