Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.
Weather data with weatherapi-client
Motoko bindings for WeatherAPI.com, generated from its OpenAPI spec. All nine operations live in a single module, `Apis/APIsApi`, and all nine are read-only GETs.
Backend
A canister that reads the current temperature and a three-day maximum series. Non-replicated is the default, so you only supply the key:
import { realtimeWeather; forecastWeather } "mo:weatherapi-client/Apis/APIsApi";
import { type Config; defaultConfig } "mo:weatherapi-client/Config";
import Array "mo:core/Array"; // in scope so `days.map(…)` dot notation resolves
persistent actor {
// The key is a query-string credential. Hold it in a stable variable set by
// an admin call; never hard-code it in source.
func config(apiKey : Text) : Config = { defaultConfig with auth = ?#apiKey apiKey };
// `q` is any WeatherAPI location query: "Zurich", "47.37,8.55", a postcode,
// an IATA code, or "auto:ip".
public func currentTempC(apiKey : Text, q : Text) : async ?Float {
let res = await* realtimeWeather(config apiKey, q, "");
do ? { res.current!.temp_c! };
};
// Daily maxima for the next three days.
public func maxTempsC(apiKey : Text, q : Text) : async [?Float] {
let res = await* forecastWeather(config apiKey, q, #_3_, "", 0, 0, "", "no", "no", 0);
let ?forecast = res.forecast else return [];
let ?days = forecast.forecastday else return [];
days.map(func(d) = do ? { d.day!.maxtemp_c! });
};
}The nine operations
| Function | Endpoint | Returns |
|---|---|---|
realtimeWeather(cfg, q, lang) | /current.json | RealtimeWeather200Response |
forecastWeather(cfg, q, days, dt, unixdt, hour, lang, alerts, aqi, tp) | /forecast.json | ForecastWeather200Response |
historyWeather(cfg, q, dt, unixdt, endDt, unixendDt, hour, lang) | /history.json | FutureWeather200Response |
futureWeather(cfg, q, dt, lang) | /future.json | FutureWeather200Response |
marineWeather(cfg, q, days, dt, unixdt, hour, lang) | /marine.json | MarineWeather200Response |
astronomy(cfg, q, dt) | /astronomy.json | Astronomy200Response |
timeZone(cfg, q) | /timezone.json | Location |
ipLookup(cfg, q) | /ip.json | Ip |
searchAutocompleteWeather(cfg, q) | /search.json | [Search] |
days is an enum, not a number: ForecastWeatherDaysParameter is #_1_ … #_14_ and MarineWeatherDaysParameter is #_1_ … #_7_ (the underscores are how the generator escapes numeric enum values — #_3_, not #_3 or 3).
API key setup
- Sign up at weatherapi.com — the
free tier covers current weather, 3-day forecast, astronomy, timezone, search and IP lookup. History, future, marine and 14-day forecasts need a paid plan and return 403 on free keys.
- Copy the key from the dashboard and pass it as
auth = ?#apiKey key. - The client appends it as
?key=…(WeatherAPI takes noAuthorization
header), so it appears in the request URL. Keep it in a stable variable written by an admin-only call, and never log the built URL.
Calls are non-replicated by default
The package ships is_replicated = ?false in defaultConfig, and that is a correctness requirement here, not just a cost saving. Every response carries per-request clocks — Location.localtime / localtime_epoch, Current.last_updated / last_updated_epoch — which change second to second. A replicated outcall has every subnet node issue its own request and demands bit-identical bodies, so those fields would break consensus on most calls while burning ~13× the cycles. You don't set it yourself; the default is correct.
Override with is_replicated = ?true only together with a transform that strips the volatile fields.
Everything is optional
WeatherAPI marks no response field required, so the generated models are all-optional: RealtimeWeather200Response.current : ?Current, Current.temp_c : ?Float, and so on. Reach through them with a do ? block (do ? { res.current!.temp_c! }) rather than nested switches, and decide what an absent field means for your caller — the API omits fields your plan does not cover (for example air_quality without the aqi=yes parameter).
Empty string and zero mean "omit"
Optional query parameters are dropped when they are "" or 0, because WeatherAPI rejects empty lang= and zero-valued numerics. So passing "" for lang, dt, alerts, aqi and 0 for unixdt, tp is how you say "not supplied" — there is no ?Text parameter to leave null.
One consequence worth knowing: `hour = 0` does not select midnight, it omits the hour filter entirely and you get the whole day's hourly array. Filter the returned hour : ?[ForecastForecastdayInnerHourInner] yourself if you need 00:00.
Errors
Non-2xx responses and decode failures throw Error.reject(…). The client is generated with diagnostics, so the message is HTTP <status> body[<n>B]=<first 100 chars>: <reason> — enough to tell a 401 (bad key) from a 403 (endpoint not on your plan) from a 400 (q not resolvable) without extra logging. Catch with try/catch and surface Error.message(err).

