API Documentation
Department of Meteorological Services
Portal
DMS Weather API v1

The Department of Meteorological Services (DMS) Weather API provides programmatic read access to Uganda's official weather forecast data — daily, city, marine and seasonal forecasts, plus lookup/search endpoints and early warning alerts. The API follows REST conventions and returns JSON (alerts are also available as CAP-compliant XML — see Early Warnings).

Base URL: https://wids.mwe.go.ug/api/v1    All endpoints below are relative to this base. Responses are application/json unless noted otherwise (the CAP alert endpoints return XML).
Authentication

Every /forecast/* and /search/* endpoint requires a bearer token. Pass it one of two ways:

Option 1 — Authorization Header (recommended)
HTTP Authorization: Bearer your_token_here
Option 2 — Query Parameter (simple GET clients)
URL https://wids.mwe.go.ug/api/v1/forecast/daily?token=your_token_here
Never expose your token in frontend JavaScript running in a browser. Call the API from your own server and pass the data on to your client instead. Tokens are shown once at creation in the API Portal — store it somewhere safe.
Response Format

Every JSON endpoint (forecast/search) wraps its payload the same way. Success responses put the actual result under data, with any endpoint-specific extras (counts, filters, issue dates) merged in alongside it:

JSON — success shape { "status": "ok", "timestamp": "2026-09-24T09:12:00+03:00", // ...endpoint-specific meta fields (forecast_type, issue_date, result_count, filter, etc.)... "data": /* object or array — see each endpoint below */ }

Error responses (see Errors & Codes) always use status: "error" with a message string instead — there is no separate numeric code field in the body; check the HTTP status code itself.

Error Codes

Errors return the HTTP status below with a JSON body of {"status":"error","message":"..."}.

HTTP StatusMeaning
200 OKSuccess
401 UnauthorizedNo token provided, token invalid, or token revoked
403 ForbiddenToken doesn't have the forecast:read scope
404 Not FoundNo data available for the request (e.g. no approved forecast yet, or filter matched nothing)
429 Too Many RequestsDaily rate limit exceeded for this token
JSON — 401 { "status": "error", "message": "No API token provided. Pass your token as: Authorization: Bearer <token>", "timestamp": "2026-09-24T09:12:00+03:00", "docs": "https://wids.mwe.go.ug/portal/api-docs" }
Rate Limiting

Each token has a configurable daily request limit, set when the token is created in the API Portal. The limit resets at midnight, server local time (Africa/Kampala).

When a token has a limit configured, these headers are included on every response:

HTTP Headers X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 847 X-Token-Name: Partner App
Exceeding the limit returns 429 Too Many Requests with X-RateLimit-Remaining: 0. A token with no daily limit configured has no rate-limit headers at all.
Daily Forecast

The latest approved daily forecast: overview, outlook text, and a period-by-period (4 periods/day) summary averaged across all regions.

GET /forecast/daily Also aliased as /forecast/latest · Requires forecast:read
Token required · Scope: forecast:read

No query parameters.

200 Success
404 Not Found
JSON 200 { "status": "ok", "timestamp": "...", "forecast_type": "daily", "data": { "forecast_id": 142, "valid_from": "2026-09-24", "valid_to": "2026-09-25", "issue_date": "2026-09-24", "issued_at": "2026-09-24 05:47:22", "updated_at": null, "issued_by": "John Doe", "approved_by": "Jane Smith", "weather_overview": "Partly cloudy skies over most of Uganda...", "further_outlook": "Rain expected in the afternoon over central...", "current_period": 2, "periods": [ { "period": 1, "label": "Early Morning", "range": "12:00 AM – 6:00 AM", "avg_temp": 18.5, "condition": "Partly Cloudy", "icon_url": "...", "wind_dir": "NE", "wind_str": "Light" } ] } }
JSON 404 { "status": "error", "message": "No approved daily forecast available.", "timestamp": "2026-09-25T09:12:00+03:00" }
Daily Forecast — Per-Region

The same latest approved forecast, broken down per region across all 4 periods — optionally narrowed to the region(s) containing a given district.

GET /forecast/daily/regions Requires forecast:read
Token required · Scope: forecast:read
ParameterTypeRequiredDescription
districtstringoptionalPartial district name. Resolves to that district's region(s) and returns only those; 404 if no district matches.

Returns data.regions[], each with a periods[] array (4 entries). Meta includes region_count, filter_district, and matched_districts (only populated when district was passed).

Daily Forecast — Per-District

The same latest approved forecast, with each response entry keyed by district rather than region. A district's forecast is inherited from its region's values (the underlying forecast is still produced per region), so this endpoint just saves callers the region → district resolution.

GET /forecast/daily/districts Requires forecast:read
Token required · Scope: forecast:read
ParameterTypeRequiredDescription
districtstringoptionalPartial district name. Omit to get every district (~137); 404 if the filter matches none.

Returns data[] — one entry per district, each with district_id, district_name, region, and a periods[] array (4 entries). Meta includes district_count and filter.

City Forecast

The latest approved 5-day city forecast, for all cities or filtered to one.

GET /forecast/city Requires forecast:read
Token required · Scope: forecast:read
ParameterTypeRequiredDescription
citystringoptionalCase-insensitive partial city name match. 404 if nothing matches.
JSON 200 — data[] [{ "city_id": 1, "city_name": "Kampala", "days": [ { "forecast_date": "2026-09-25", "min_temp": 17, "max_temp": 28, "condition": "Partly Cloudy", "icon_url": "..." } ] }]
Marine Forecast

The latest approved Lake Victoria marine forecast, broken down per zone (wind, waves, visibility, hazard).

GET /forecast/marine Requires forecast:read
Token required · Scope: forecast:read
ParameterTypeRequiredDescription
landing_sitestringoptionalPartial landing-site name — narrows to the zone(s) containing it. 404 if none matches.

Returns data.zones[] with wind/wave/visibility/hazard fields plus matching icon URLs, alongside advisory_level and general_situation at the top level.

Seasonal Outlook

The latest issued seasonal forecast — overview, sector advisories, and a lightweight per-sub-region summary. Check in_season to see whether it actually covers the current month.

GET /forecast/seasonal Requires forecast:read
Token required · Scope: forecast:read

No query parameters. Returns the season's narrative fields (ITCZ, MJO, ENSO, Lake Victoria status), areas[] (per sub-region summary) and sector_advisories[].

This endpoint always returns the most recently issued seasonal forecast, even if it's from a past season — it does not withhold data the way the USSD service does. Always check the boolean in_season field before presenting it as current.
Seasonal Outlook — Areas

Sub-region breakdown of the latest seasonal forecast: rainfall category, onset/peak/cessation dates, and the district list for each sub-region.

GET /forecast/seasonal/areas Requires forecast:read
Token required · Scope: forecast:read
ParameterTypeRequiredDescription
districtstringoptionalMatches against the sub-region's district list. 404 if none matches.
Regions / Cities / Landing Sites / Districts

Reference lookups for populating dropdowns — all require forecast:read.

EndpointOptional filterReturns
GET /search/regions—All regions, each with its districts nested inside
GET /search/cities—All registered cities
GET /search/landing_siteszone_id integerMarine landing sites, with district + zone context
GET /search/districtssub_region_id integer, region stringDistricts with their seasonal sub-region/region
Early Warning Alerts (CAP)

A separate, unauthenticated set of endpoints for active early-warning alerts, following the WMO Common Alerting Protocol (CAP) v1.2 standard. No token, no Authorization header — these are open by design so any aggregator can consume them.

These live at https://wids.mwe.go.ug/api/v1/cap/* — not under the token-authenticated /forecast/* namespace above.
EndpointFormatDescription
GET /cap/feedapplication/atom+xmlAtom feed of all currently active, non-expired alerts.
GET /cap/latestapplication/cap+xmlThe single most severe active alert as CAP XML. 204 No Content if none are active.
GET /cap/{id}application/cap+xmlCAP XML for one alert by ID. Cancelled alerts return a CAP Cancel message.
GET /cap/allapplication/jsonJSON list of active alerts with a cap_xml_url and public html_url per alert.
JSON — /cap/all { "status": "ok", "count": 1, "feed": "https://wids.mwe.go.ug/api/v1/cap/feed", "alerts": [{ "id": 12, "cap_id": "...", "title": "Heavy Rainfall Warning", "severity": 3, "severity_label": "Warning", "area_names": ["Central"], "valid_from": "...", "valid_to": "...", "cap_xml_url": "...", "html_url": "..." }] }
Scopes

Today there is a single scope. Every /forecast/* and /search/* endpoint requires it; a token missing it gets 403 Forbidden. The CAP alert endpoints need no scope or token at all.

ScopeApplies to
forecast:readAll endpoints under /forecast/*, /search/*
Code Examples

Every example below fetches the same thing — the current daily forecast — so you can compare languages directly.

cURL
JavaScript
PHP
Python
bash curl -H "Authorization: Bearer YOUR_TOKEN" \ "https://wids.mwe.go.ug/api/v1/forecast/daily"
JavaScript // Server-side only — never call this directly from browser JS, // it would expose your token. Fetch it from your own backend instead. const res = await fetch('https://wids.mwe.go.ug/api/v1/forecast/daily', { headers: { 'Authorization': 'Bearer YOUR_TOKEN' } }); const body = await res.json(); if (!res.ok) { throw new Error(body.message); } // body.status === 'error' console.log(body.data.weather_overview);
PHP // ignore_errors keeps the error body instead of file_get_contents // silently returning false on a 401/404 $response = file_get_contents('https://wids.mwe.go.ug/api/v1/forecast/daily', false, stream_context_create(['http' => [ 'header' => "Authorization: Bearer YOUR_TOKEN\r\n", 'ignore_errors' => true, ]]) ); if ($response === false) { throw new Exception('Request failed — no connection.'); } $body = json_decode($response, true); if ($body['status'] !== 'ok') { throw new Exception($body['message']); } echo $body['data']['weather_overview'];
Python import requests res = requests.get( "https://wids.mwe.go.ug/api/v1/forecast/daily", headers={"Authorization": "Bearer YOUR_TOKEN"} ) res.raise_for_status() # raises on 401/404/429/etc instead of failing later body = res.json() print(body["data"]["weather_overview"])