DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for The missing WeatherKit REST API Docs
Simon Barker
Simon Barker

Posted on • Originally published at allthecode.co

The missing WeatherKit REST API Docs

WeatherKit is Apple's new weather data service, announced at WWDC 2022 it is the their answer to privacy focussed weather data. Not only have they created this for use in native iOS Swift apps but they have also created a REST API so that web developers are able to access it as well.

The WeatherKit REST API Documentation is fairly limited on how to get this set up so here is a guide to get up you up and running with Apple's WeatherKit. In fact, I had a labs call with two Apple engineers to get this working which was pretty cool. We will build a simple ExpressJS server and a index.html page to display weather for a set location.

WeatherKit REST API setup

The REST API requires a signed JWT to be attached to each request. To set this up you need:

  • A paid developer account
  • An App Identifier
  • A key

You can set up an App Identifier on the Identifiers page of your developer account. You need to create a new App ID of type App, give it a Bundle ID in reverse-domain name style, so com.myapp.weather or similar, and then make sure you select WeatherKit from the App Services tab. This App Identifier can take about 30 minutes to propagate through Apple's systems.

For the key you go to the Keys page in your developer account and add a new key with WeatherKit selected. Remember to download the key file you get at the end!

WeatherKit REST API JWT

Now you have your key and App Identifier we can create a server to sign that key. You will be signing the JWT yourself so you will need a backend/server where you can execute your code, as always do not expose your key to the browser or client

So, let's create a little ExpressJS server. Comments in the code explain what is going on. I'm assuming you know how to use ExpressJS already.

const express = require("express");
const axios = require("axios");
const fs = require("fs");
const jwt = require("jsonwebtoken");
var cors = require("cors");

const app = express();
const port = 3000;

app.use(cors());

app.get("/", async (req, res) => {
  // This is the key file you downloaded from Apple
  var privateKey = fs.readFileSync("YOUR KEY FILE FROM DEVELOPER CENTER.p8");

  // Creating the signed token needs specific data
  var token = jwt.sign(
    {
      subject: "APP ID", // the reverse URL App Id you made above
    },
    privateKey,
    {
      jwtid: "YOUR TEAM ID.YOUR APP ID", // you can find your TeamID in the top right corner of your developer account, then put a `.` and then put in your reverse URL App Id
      issuer: "TEAM ID", // find your TeamID in your developer account
      expiresIn: "1h", // give it 1 hour of validity
      keyid: "KEY ID", // this is the ID for the key you created
      algorithm: "ES256", // this is the algorithm Apple used
      header: {
        // see details below for this
        id: "YOUR TEAM ID.YOUR APP ID",
      },
    }
  );

  // log your token so you can decode it and manually check it's ok, more below on this
  //console.log(token);

  const url =
    "https://weatherkit.apple.com/api/v1/weather/en/51.677612/-2.937941?dataSets=currentWeather&timezone=Europe/London";

  // add the token to your headers
  const config = {
    headers: { Authorization: `Bearer ${token}` },
  };

  // get the data
  const { data: weatherData } = await axios.get(url, config);

  // prove we have data
  //console.log(weatherData);

  // return the data to your front end
  res.json(weatherData);
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});
Enter fullscreen mode Exit fullscreen mode

Your decoded JWT needs to look like this:

{
  "header": {
    "alg": "ES256",
    "typ": "JWT",
    "kid": "KEY_ID",
    "id": "TEAM_ID.APP_ID" // this is the bit that has been tricking people up
  },
  "payload": {
    "subject": "APP_ID",
    "iat": 1654823790,
    "exp": 1654827390,
    "iss": "TEAM_ID",
    "jti": "TEAM_ID.APP_ID"
  },
  "signature": "d98jNki1tXX3mSYOAPp-Wpds8kEbGJ-lhKPtasdfSqoDyiJ2WbeI6U73UWCYWQgISlDOzaXdI5rSrSFcrg5g"
}
Enter fullscreen mode Exit fullscreen mode

To get id in the header I needed to set it manually when creating the JWT, Apple is looking for id in the header and depending on your JWT signing library of choice that may not (probably won't) be there by default.

 header: {
    id: "YOUR TEAM ID.YOUR APP ID", // see details below for this
},
Enter fullscreen mode Exit fullscreen mode

WeatherKit REST API Front end

To call the serve end point you can use the code below in an index.html to make a simple front end that starts with default data and then adds in the data from weather kit.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.0.0-alpha.1/axios.min.js"
      integrity="sha512-xIPqqrfvUAc/Cspuj7Bq0UtHNo/5qkdyngx6Vwt+tmbvTLDszzXM0G6c91LXmGrRx8KEPulT+AfOOez+TeVylg=="
      crossorigin="anonymous"
      referrerpolicy="no-referrer"
    ></script>
    <script src="https://cdn.tailwindcss.com"></script>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div class="container max-w-3xl mx-auto p-8">
      <h1 class="text-3xl text-center font-bold text-gray-700">All The Code</h1>
      <div
        class="shadow-lg shadow-black/40 w-[400px] p-4 mx-auto mt-10 rounded flex flex-col gap-y-6"
      >
        <h2 class="text-xl font-bold text-center">Current Weather</h2>
        <div class="text-7xl font-black text-center">
          <span id="temperature">0</span> <span class="font-thin">&#8451;</span>
        </div>
        <div class="flex flex-row justify-between">
          <div class="flex flex-col text-center w-1/3">
            <div id="wind" class="text-3xl font-bold">17.5</div>
            <div class="text-3xl">Wind</div>
          </div>
          <div class="flex flex-col text-center w-1/3">
            <div id="uv" class="text-3xl font-bold">0</div>
            <div class="text-3xl">UV</div>
          </div>
          <div class="flex flex-col text-center w-1/3">
            <div class="text-3xl font-bold"><span id="cloud">81</span>%</div>
            <div class="text-3xl">Cloud</div>
          </div>
        </div>
      </div>
      <div class="text-center mt-10">
        Weather data provided by
        <a href="https://weather-data.apple.com/legal-attribution.html"
          >Apple WeatherKit</a
        >
      </div>
    </div>
  </body>

  <script>
axios
  .get("http://localhost:3000", {
    headers: { "Access-Control-Allow-Origin": "*" },
  })
  .then((res) => {
    let temp = document.getElementById("temperature");

    temp.innerHTML = res.data.currentWeather.temperature;

    if (parseFloat(res.data.currentWeather.temperature) < 15) {
      temp.classList.add("text-blue-500");
    } else {
      temp.classList.add("text-red-500");
    }

    document.getElementById("wind").innerHTML =
      res.data.currentWeather.windSpeed;
    document.getElementById("uv").innerHTML = res.data.currentWeather.uvIndex;
    document.getElementById("cloud").innerHTML =
      res.data.currentWeather.cloudCover * 100;
  });
  </script>
</html>
Enter fullscreen mode Exit fullscreen mode

When this page loads it will request weather data from Apple via your server with a sign JWT 😊

If you want to see this widget in action you can see it on this page on my site

If you're still learning to code and some, or all of this, doesn't make sense then head over to All The Code where I teach people how to be amazing web developers. Join my mailing list for a free coding quick start guide and weekly tip and advice.

Top comments (1)

Collapse
 
jordan_millar_4fb7ec01aa8 profile image
Jordan Millar

Have you been able to get it working for historical dates? Works fine for current date but can not get it working for historical dates. Perhaps this is not implemented in the REST API?

🌚 Life is too short to browse without dark mode