Skip to content
Partner API · v1

Build on Tahadhari

Tahadhari is Mandera County’s two-way flood early-warning and response system. The Partner API lets your organisation’s systems see incidents in the wards where you work, know how far each one can be trusted and why, send in reports from your own tools, add evidence, and coordinate your response with the county and other agencies.

  1. 1Report

    A citizen, volunteer or partner system reports what they see by app, SMS, USSD, WhatsApp, voice or this API.

  2. 2Credibility

    Reports of the same problem within 1.5 km and 12 hours are grouped into one incident and scored: independent reporters, channels, GPS, sensors, news, partners.

  3. 3Verification

    A chief or field officer checks on the ground. Confirmed, disputed or false; partners can add their own field confirmation.

  4. 4Publication

    The county decides what the public sees. Anything not confirmed carries a clear “not verified” warning.

  5. 5Response

    Organisations offer interventions; the county names a lead agency and supporting agencies, and duplicated effort is flagged.

The API gives you each stage: reports go in through POST /reports, credibility and verification come out on every incident, publication is shown as publicStatus, and the response is coordinated through /incidents/{id}/responses. Webhooks tell you when anything changes in your areas.

Quick start

Base URL for every endpoint below:

Base URL
https://elnino.medicdecode.com/api/partner/v1

Set two variables once, then every example on this page runs as-is:

Shell
export TAHADHARI_URL="https://elnino.medicdecode.com/api/partner/v1"
export TAHADHARI_KEY="th_live_xxxxxxxx_…"   # from your partner coordinator
Check your key
curl -H "Authorization: Bearer $TAHADHARI_KEY" "$TAHADHARI_URL/me"

If that returns your organisation’s name and wards, you are ready. Poll GET /incidents?since=… every minute or two, or register a webhook in the partner portal and fetch the incident when an event arrives.

Authentication

Every request (except openapi.json) needs an API key in the Authorization header:

Header
Authorization: Bearer th_live_<prefix>_<secret>
  • Who issues keys. Your partner coordinator creates keys in the Tahadhari partner portal (sign in at /signin). Organisations without a coordinator account can ask the Mandera County EOC for one.
  • One organisation per key. A key acts for its organisation. What it can see follows your registered operating areas; what it can do follows its scopes.
  • Shown once. Only a hash is stored. Copy the key when it is created; if it is lost, revoke it and create a new one. The visible prefix (th_live_bfd7a2ce) identifies it in the portal.
  • Actions are attributed.Offers, evidence and comments sent with a key appear under the name of the person who created it, with your organisation and “(via API)”, and are written to the audit trail with the key id.
  • Keep it server-side. CORS is open so internal dashboards can call the API, but never ship a key in a public web page or mobile app.

Scopes

Ask for only the scopes your integration needs. A missing scope returns 403.

ScopeAllows
incidents:readRead incidents in your organisation's areas (with credibility and responses)
incidents:read_allRead incidents in every ward
reports:writeSubmit reports from your systems (Kobo, ODK, call centres)
responses:writeOffer and update your organisation's responses
evidence:writeAdd evidence (e.g. field confirmation, news links) and comments
alerts:readRead public alerts
any keyGET /me and GET /organisations work with any valid key.

Endpoints

GET

/api/partner/v1/me

Who am I

scope: any key

Your organisation, the key’s scopes and prefix, your operating areas and every ward they cover. Call it first to check a new key.

Request
curl -H "Authorization: Bearer $TAHADHARI_KEY" \
  "$TAHADHARI_URL/me"
Response
{
  "organisation": { "id": "wash-a", "name": "WASH Partner A", "kind": "ngo" },
  "key": { "id": "ak-demo", "name": "Kobo bridge", "prefix": "th_live_bfd7a2ce",
           "createdAt": "2026-09-20T08:00:00.000Z", "lastUsedAt": "2026-09-23T13:40:02.114Z" },
  "scopes": [{ "scope": "incidents:read", "description": "Read incidents in your organisation's areas …" }],
  "operatingAreas": [{ "scope": "ward", "id": "neboi", "name": "Neboi" }],
  "wards": [{ "id": "neboi", "name": "Neboi", "subCounty": { "id": "mandera-east", "name": "Mandera East" } }]
}
GET

/api/partner/v1/incidents

List incidents

scope: incidents:read

Incidents in your operating areas, newest update first, each with its credibility, publication status, lead agency, responses, report count and channels. scope=all covers every ward and needs incidents:read_all.

Query parameterMeaning
scopearea (default) or all
statuscomma-separated: open, verified, responding, resolved, dismissed
verificationcomma-separated: single_source, corroborated, confirmed, disputed, false
sinceISO time; only incidents updated at or after it (use for polling)
wardone ward id (must be yours with scope=area)
limit1–200, default 50
cursornextCursor from the previous page
Request
curl -H "Authorization: Bearer $TAHADHARI_KEY" \
  "$TAHADHARI_URL/incidents?status=open,verified,responding&since=2026-09-23T06:00:00Z&limit=20"
Response
{
  "data": [{
    "id": "in-01",
    "title": "Houses flooded near Neboi market, families on rooftops",
    "ward": { "id": "neboi", "name": "Neboi" },
    "subCounty": { "id": "mandera-east", "name": "Mandera East" },
    "category": "house_flooded", "categoryLabel": "House flooded",
    "severity": "life_threatening", "status": "responding",
    "lat": 3.9312, "lng": 41.8611,
    "createdAt": "2026-09-23T10:02:11.000Z", "updatedAt": "2026-09-23T12:49:12.787Z", "resolvedAt": null,
    "credibility": {
      "verification": "confirmed", "label": "Confirmed",
      "explain": "Checked on the ground by a named official or field officer.",
      "confidence": 86, "distinctReporters": 3,
      "breakdown": [
        { "label": "3 independent reporters", "points": 36 },
        { "label": "3 different channels", "points": 6 },
        { "label": "GPS location", "points": 4 },
        { "label": "Confirmed on the ground by an official", "points": 40 }
      ],
      "warning": null
    },
    "publicStatus": "published", "publicNote": null,
    "leadOrg": { "id": "krcs", "name": "Kenya Red Cross (Mandera branch)" },
    "responses": [
      { "org": { "id": "krcs", "name": "Kenya Red Cross (Mandera branch)" }, "status": "lead",
        "interventions": ["rescue_boat", "first_aid"] }
    ],
    "reportCount": 3,
    "channels": ["app", "sms", "whatsapp"]
  }],
  "nextCursor": "MjAyNi0wOS0yMyAxMjo0OToxMi43ODcrMDB8aW4tMDE",
  "scope": "area"
}
GET

/api/partner/v1/incidents/{id}

One incident

scope: incidents:read

Everything in the list item (with responses expanded) plus report aggregates, evidence (kind, status, supports, title, source, link, organisation), full responses (interventions, ETA, resources, overlaps with other agencies) and the discussion thread. 404 if it is outside your areas (unless your key has incidents:read_all or your organisation responds to it).

Request
curl -H "Authorization: Bearer $TAHADHARI_KEY" \
  "$TAHADHARI_URL/incidents/in-01"
Response
{
  "data": {
    "id": "in-01", "title": "…", "ward": { "…": "…" }, "credibility": { "…": "…" },
    "reports": { "firstAt": "2026-09-23T10:02:11.000Z", "lastAt": "2026-09-23T10:31:40.000Z",
                 "helpRequests": 2, "withGps": 1 },
    "evidence": [
      { "id": "ev-3k1", "kind": "official", "status": "accepted", "supports": true,
        "title": "Confirmed on site by Chief Ibrahim Adan", "source": null, "url": null,
        "at": "2026-09-23T11:05:00.000Z", "org": "Mandera County EOC" }
    ],
    "responses": [
      { "id": "rs-1", "org": { "id": "krcs", "name": "Kenya Red Cross (Mandera branch)" },
        "status": "lead", "statusLabel": "Lead agency", "proposedLead": true,
        "interventions": [{ "id": "rescue_boat", "label": "Boat rescue" }],
        "description": "Boat team from Mandera town", "resources": "1 boat, 6 staff",
        "etaMinutes": 45, "overlaps": [], "outsideOrgArea": false, "decisionNote": null,
        "createdAt": "…", "updatedAt": "…" }
    ],
    "discussion": [
      { "id": "nt-1", "author": { "name": "Mohamed Ali", "organisation": "Mandera County EOC",
        "role": "County EOC officer" }, "body": "KRCS leads. WASH partners please coordinate here.",
        "at": "2026-09-23T11:20:00.000Z" }
    ]
  }
}
POST

/api/partner/v1/incidents/{id}/responses

Offer to respond

scope: responses:write

Offers your organisation’s help. The offer starts as “offered”; the county names a lead agency, marks others as supporting, or declines. Sending again updates your offer.

Body fieldTypeMeaning
interventionsstring[] (required)one or more intervention ids, e.g. water_trucking, rescue_boat
descriptionstring ≤ 600what you will do
resourcesstring ≤ 300vehicles, kits, staff
etaMinutesintegerminutes until you can be on site
proposeLeadbooleanoffer to lead the response
Request
curl -X POST -H "Authorization: Bearer $TAHADHARI_KEY" \
  -H "Content-Type: application/json" \
  "$TAHADHARI_URL/incidents/in-01/responses" \
  -d '{"interventions":["water_trucking","chlorination"],"resources":"2 bowsers, 4 staff","etaMinutes":90}'
Response
{
  "data": { "id": "rs-8k2j1x", "incidentId": "in-01",
    "org": { "id": "wash-a", "name": "WASH Partner A" },
    "status": "offered", "statusLabel": "Offered", "proposedLead": false,
    "interventions": ["water_trucking", "chlorination"], "description": "",
    "resources": "2 bowsers, 4 staff", "etaMinutes": 90,
    "createdAt": "2026-09-23T13:02:00.000Z", "updatedAt": "2026-09-23T13:02:00.000Z" }
}
PATCH

/api/partner/v1/responses/{id}

Update your response

scope: responses:write

Moves your own organisation’s response to on_site, completed or withdrawn, with an optional note posted to the thread. on_site and completed need the county to have accepted the offer (lead or supporting) first; otherwise 409.

Body fieldTypeMeaning
status"on_site" | "completed" | "withdrawn"required
notestring ≤ 500shown in the discussion thread
Request
curl -X PATCH -H "Authorization: Bearer $TAHADHARI_KEY" \
  -H "Content-Type: application/json" \
  "$TAHADHARI_URL/responses/rs-8k2j1x" \
  -d '{"status":"on_site","note":"Bowsers at Neboi primary school"}'
Response
{ "data": { "id": "rs-8k2j1x", "status": "on_site", "statusLabel": "On site", "…": "…" } }
POST

/api/partner/v1/incidents/{id}/evidence

Add evidence

scope: evidence:write

kind "partner" — your own staff confirm (or, with supports:false, contradict) the incident — counts immediately. "media" and "other" wait for a county reviewer. Returns the incident’s credibility after the change.

Body fieldTypeMeaning
kind"partner" | "media" | "other"required
titlestring 3–200required, what the evidence says
urlhttps URLlink to the article or record
sourcestring ≤ 120defaults to the link’s host name
notestring ≤ 1000details
supportsbooleandefault true; false contradicts the incident
Request
curl -X POST -H "Authorization: Bearer $TAHADHARI_KEY" \
  -H "Content-Type: application/json" \
  "$TAHADHARI_URL/incidents/in-01/evidence" \
  -d '{"kind":"partner","title":"Our WASH officer confirms 40 households displaced"}'
Response
{
  "data": { "incidentId": "in-01", "status": "accepted",
    "message": "Recorded. It counts towards credibility now.",
    "credibility": { "verification": "corroborated", "confidence": 61, "…": "…" } }
}
POST

/api/partner/v1/incidents/{id}/notes

Comment on an incident

scope: evidence:write

Posts to the incident’s discussion thread under the name of the person who created the key, with your organisation, marked “(via API)”.

Body fieldTypeMeaning
bodystring 2–1000required
Request
curl -X POST -H "Authorization: Bearer $TAHADHARI_KEY" \
  -H "Content-Type: application/json" \
  "$TAHADHARI_URL/incidents/in-01/notes" \
  -d '{"body":"Our team can reach Neboi by 14:00 if the Rhamu road is open."}'
Response
{ "data": { "incidentId": "in-01", "body": "Our team can reach … (via API)", "at": "2026-09-23T13:10:00.000Z" } }
POST

/api/partner/v1/reports

Submit a report

scope: reports:write

Pushes a report from your own system (Kobo, ODK, a call centre) through the same intake as SMS and WhatsApp: placed by GPS or ward, triaged, grouped with open incidents of the same kind within 1.5 km and 12 hours, and scored. Give wardId or both lat and lng.

Body fieldTypeMeaning
textstring 3–2000required, what was observed
kind"hazard" | "help"default hazard; help = someone needs help
lang"en" | "sw" | "so"default en
wardIdstringrequired unless lat and lng are given
lat, lngnumberGPS; wins over wardId
placeNamestring ≤ 160village, landmark
categorystringroad_cut, house_flooded, people_stranded, livestock, water_sanitation, health, infrastructure, rumour, other
reporterPhonestringoptional; only for de-duplication and track record, never returned
externalIdstring ≤ 120your record id, echoed back
Request
curl -X POST -H "Authorization: Bearer $TAHADHARI_KEY" \
  -H "Content-Type: application/json" \
  "$TAHADHARI_URL/reports" \
  -d '{"text":"Bridge on the Rhamu road partly submerged","lat":3.9331,"lng":41.2144,"category":"infrastructure","externalId":"kobo-58213"}'
Response
{
  "data": { "ref": "MDR-4532", "reportId": "rp-x81k2", "incidentId": "in-h9s1", "grouped": false,
    "ward": { "id": "rhamu", "name": "Rhamu" }, "category": "infrastructure", "severity": "moderate",
    "credibility": { "verification": "single_source", "label": "Unverified: one report",
      "confidence": 16, "distinctReporters": 1,
      "warning": "Not verified. Only one person has reported this so far.", "…": "…" },
    "externalId": "kobo-58213" }
}
GET

/api/partner/v1/alerts

Active alerts

scope: alerts:read

Sent, unexpired alerts, most severe first, in English, Kiswahili and Somali. Optional ward filter.

Query parameterMeaning
wardonly alerts covering this ward
Request
curl -H "Authorization: Bearer $TAHADHARI_KEY" \
  "$TAHADHARI_URL/alerts?ward=township"
Response
{
  "data": [{ "id": "al-001", "hazard": "flood", "level": 3, "levelLabel": "Warning",
    "wards": [{ "id": "township", "name": "Township" }],
    "title": { "en": "Flood warning: Daua river rising", "sw": "Onyo la mafuriko: …", "so": "Digniin daad: …" },
    "body": { "en": "…", "sw": "…", "so": "…" },
    "actions": { "en": ["Move to higher ground"], "sw": ["…"], "so": ["…"] },
    "issuer": "Mandera County EOC", "effective": "…", "expires": "…", "sentAt": "…",
    "channels": ["cell_broadcast", "sms"], "capUrl": "/api/cap/alerts/al-001" }]
}
GET

/api/partner/v1/organisations

Who operates where

scope: any key

Every organisation with its services, operating areas, the wards those cover and the number of field officers in each ward. Counts only, no staff names.

Query parameterMeaning
wardonly organisations operating in this ward
Request
curl -H "Authorization: Bearer $TAHADHARI_KEY" \
  "$TAHADHARI_URL/organisations?ward=neboi"
Response
{
  "data": [{ "id": "krcs", "name": "Kenya Red Cross (Mandera branch)", "kind": "red_cross",
    "description": "…", "services": [{ "id": "rescue_boat", "label": "Boat rescue" }],
    "contact": { "phone": "…", "email": "…" },
    "operatingAreas": [{ "scope": "sub_county", "id": "mandera-east", "name": "Mandera East" }],
    "wards": [{ "id": "neboi", "name": "Neboi", "fieldOfficers": 3 }],
    "fieldOfficersByWard": { "neboi": 3 },
    "members": { "total": 9, "field": 6, "desk": 3 }, "activeResponses": 2 }]
}
GET

/api/partner/v1/openapi.json

OpenAPI document

scope: none

OpenAPI 3.1 description of every endpoint, schema and webhook. No key needed. Load it into Postman, Swagger UI or a code generator.

Request
curl "$TAHADHARI_URL/openapi.json"
Response
{ "openapi": "3.1.0", "info": { "title": "Tahadhari Partner API", "version": "1.0.0" }, "…": "…" }

Intervention ids

Used in offers, responses and organisation services.

  • search_rescueSearch and rescue
  • rescue_boatBoat rescue
  • evacuationEvacuation / transport
  • first_aidFirst aid
  • medical_teamMedical team
  • ambulanceAmbulance / referral
  • water_truckingWater trucking
  • chlorinationWater treatment / chlorination
  • latrinesLatrines and hygiene
  • shelterTents and shelter
  • nfiHousehold kits (NFIs)
  • foodFood rations / hot meals
  • cashCash transfers
  • livestockLivestock rescue / feed
  • road_clearingRoad clearing / repair
  • psychosocialPsychosocial support
  • assessmentNeeds assessment

Pagination

Lists return data and nextCursor. Pass nextCursor back as cursor (with the same filters) until it is null. Pages are stable while you walk them: new updates appear at the top of the first page, so for syncing use since set to the time you last started a sync.

Credibility model

Every incident carries a confidence score from 0 to 100 and a verification level, recomputed whenever evidence changes. The score is a transparent sum of named contributions, returned as breakdown, so your staff can see why an incident is trusted or not.

LevelLabelMeaning
single_sourceUnverified: one reportOnly one person has reported this. It has not been checked.
corroboratedCorroboratedReported independently by several people or backed by sensors or news, but not yet checked on the ground by an official.
confirmedConfirmedChecked on the ground by a named official or field officer.
disputedDisputedA responder on site, or several people in the ward, say it is not as reported. Treat with caution.
falseFalse reportChecked and found to be untrue (for example a rumour).

What raises confidence

SignalPointsHow
Independent reportersup to 40Each distinct person adds 12 × (0.5 + their track record). Anonymous reports count at first-time weight.
Channelsup to 6The same event reported through several channels (app, SMS, WhatsApp…): 3 per extra channel.
Photo evidence8A photo the AI read as consistent with the report.
Precise location4At least one report placed by GPS.
Sensorsup to 13River gauge above alert level (+10), heavy rain forecast of 50 mm or more in 48 h (+3). Flood categories only.
News / mediaup to 20Accepted news or media coverage, 15 each.
Partner organisationup to 15A partner confirms from its own field presence (POST …/evidence with kind "partner").
Official on the ground40A chief, field officer or county officer confirmed it on site.
Other evidenceup to 10Other accepted supporting sources, 5 each.
  • Confirmed = an official confirmed it on the ground.
  • Corroborated = news or a partner confirmation, or 2+ independent reporters (or a gauge above alert level) with confidence of at least 40.
  • Otherwise single_source.
  • A “not what I see” from a responder on site makes it disputed and multiplies confidence by 0.4. “False” is set by the county and sets confidence to 0.
  • Rumour-category incidents are capped at 30 until an official confirms them.
  • Only confirmed incidents can be published without a warning; a published incident whose basis weakens drops back to “with warning” automatically.

A reporter’s track record is the share of their earlier checked reports that were confirmed, with a neutral starting point (false reports count double). It changes how much each person adds, but it is never returned by the API.

Publication

publicStatusMeaning
hiddenNot public. Visible only to responders, the county and partners.
published_with_warningPublic, with warning. Shown to the public with a clear "not verified" warning.
publishedPublic. Shown to the public as confirmed information.

credibility.warning is the exact sentence the public sees next to anything not confirmed. If you republish Tahadhari incidents, show it too.

Webhooks

Your partner coordinator registers an HTTPS endpoint and picks events in the partner portal, and receives a signing secret (whsec_…). You then get a signed JSON POST for each event in wards your organisation covers.

EventSent when
incident.createdA new incident opens in your areas
incident.updatedStatus, credibility or publication changes
incident.responseResponses offered, coordinated or updated
alert.sentAn alert is released
Delivery headers
POST /webhooks/tahadhari HTTP/1.1
Content-Type: application/json
User-Agent: Tahadhari-Webhooks/1
X-Tahadhari-Event: incident.created
X-Tahadhari-Signature: t=1790157600,v1=5f2b0c9d…   (HMAC-SHA256 of "<t>.<raw body>", hex)
Body: incident.created
{
  "id": "evt_3f9a61c2b0d4e8a7",
  "event": "incident.created",
  "created": "2026-09-23T10:02:14.120Z",
  "data": {
    "incidentId": "in-h9s1",
    "wardId": "neboi",
    "category": "house_flooded",
    "severity": "severe",
    "verification": "single_source",
    "confidence": 18,
    "reports": 1
  }
}

data.incidentIdis always present; other fields depend on what changed (verification and confidence, publicStatus, or a response’s organisation and status). Treat the event as a nudge and fetch GET /incidents/{id} for the full picture.

Verify the signature

X-Tahadhari-Signature is t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of "<t>.<raw body>" keyed with your webhook secret. Compute it over the raw bytes you received, compare in constant time, and reject timestamps more than five minutes old to stop replays.

Node.js
// Node.js 18+ (Express shown; the verify function works anywhere)
import { createHmac, timingSafeEqual } from 'node:crypto'
import express from 'express'

const SECRET = process.env.TAHADHARI_WEBHOOK_SECRET // whsec_…
const TOLERANCE_SECONDS = 300 // reject replays older than 5 minutes

export function verifyTahadhari(rawBody, header, secret = SECRET) {
  // header: "t=1790157600,v1=5f2b0c…"
  const parts = Object.fromEntries(
    (header || '').split(',').map((p) => {
      const i = p.indexOf('=')
      return [p.slice(0, i).trim(), p.slice(i + 1).trim()]
    }),
  )
  const t = Number(parts.t)
  if (!Number.isInteger(t) || !parts.v1) return false
  if (Math.abs(Date.now() / 1000 - t) > TOLERANCE_SECONDS) return false
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest()
  const given = Buffer.from(parts.v1, 'hex')
  return given.length === expected.length && timingSafeEqual(given, expected)
}

const app = express()

// Keep the raw body: the signature is over the exact bytes we sent.
app.post('/webhooks/tahadhari', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8')
  if (!verifyTahadhari(raw, req.get('X-Tahadhari-Signature'))) {
    return res.status(400).send('Invalid signature')
  }
  const event = JSON.parse(raw) // { id, event, created, data }
  res.sendStatus(204) // acknowledge within 8 seconds…
  handleLater(event) // …then do the slow work (dedupe on event.id)
})
Python
# Python 3.9+ (Flask shown; the verify function works anywhere)
import hashlib
import hmac
import os
import time

from flask import Flask, abort, request

SECRET = os.environ["TAHADHARI_WEBHOOK_SECRET"].encode()  # whsec_…
TOLERANCE_SECONDS = 300  # reject replays older than 5 minutes


def verify_tahadhari(raw_body: bytes, header: str, secret: bytes = SECRET) -> bool:
    # header: "t=1790157600,v1=5f2b0c…"
    try:
        parts = dict(p.strip().split("=", 1) for p in header.split(","))
        t = int(parts["t"])
        given = parts["v1"]
    except (KeyError, ValueError):
        return False
    if abs(time.time() - t) > TOLERANCE_SECONDS:
        return False
    expected = hmac.new(secret, f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, given)


app = Flask(__name__)


@app.post("/webhooks/tahadhari")
def tahadhari_webhook():
    # Use the raw bytes: re-serialising the JSON would change the signature.
    if not verify_tahadhari(request.get_data(), request.headers.get("X-Tahadhari-Signature", "")):
        abort(400)
    event = request.get_json()  # {"id", "event", "created", "data"}
    handle_later(event)  # dedupe on event["id"]; reply within 8 seconds
    return "", 204

Delivery, retries and pausing

  • Reply with any 2xx within 8 seconds. Acknowledge first, then process.
  • Each event is tried up to 3 times (at once, after 2 s, after 10 s); a 4xx reply other than 408/429 is not retried. Use the event id to ignore duplicates. After longer downtime, catch up with GET /incidents?since=<last event time>.
  • A non-2xx reply, a timeout or a connection error counts as a failure. A success resets the count.
  • After 20 consecutive failures the subscription is paused. Your coordinator can see the last status and re-enable it in the partner portal.
  • Order is not guaranteed and the same change can produce more than one event. De-duplicate on the event id.

Rate limits and errors

Each key may make 120 requests per minute. Errors are JSON with a plain-language message:

Error body
{ "error": "This key does not have the \"responses:write\" scope." }
StatusWhen
400The request is not valid. The message names the field and what to change, e.g. "status: use one or more of: open, verified, …".
401No key, a malformed Authorization header, or a key that is not valid or has been revoked.
403The key lacks the scope the endpoint needs, or you asked for a ward outside your areas with scope=area.
404Not found — or outside what your key may see. The API does not reveal that out-of-area incidents exist.
409The action does not fit the current state: the incident is closed, or the county has not accepted your offer yet.
429More than 120 requests in a minute from this key. Wait and retry with backoff.
500Something went wrong on our side. Retry later; tell the county EOC if it persists.

Privacy

The API never returns citizen phone numbers, reporter ids, the text of individual reports, photos or reporters’ track records. You get aggregates: report counts, channels, distinct reporters and the credibility breakdown. Staff in the discussion thread appear with their name, organisation and role; organisation directory entries show counts of field officers per ward, never names.

If you send reporterPhone with a report, it is used only to recognise the same person across channels for de-duplication and track record. It is never returned by any endpoint.

OpenAPI

The full contract — every endpoint, schema, example, error and webhook payload — is an OpenAPI 3.1 document. No key is needed to fetch it.

OpenAPI 3.1
https://elnino.medicdecode.com/api/partner/v1/openapi.json
Open openapi.json
Questions about access or keys: contact the Mandera County EOC. Public, no-key data (wards, safe sites, CAP alerts) is at /api/v1 and /api/cap/alerts.xml.