{"id":4508,"date":"2022-09-11T10:56:16","date_gmt":"2022-09-11T15:56:16","guid":{"rendered":"https:\/\/dev.iachieved.it\/iachievedit\/?p=4508"},"modified":"2024-01-14T11:10:20","modified_gmt":"2024-01-14T17:10:20","slug":"weatherkit-rest-api","status":"publish","type":"post","link":"https:\/\/dev.iachieved.it\/iachievedit\/weatherkit-rest-api\/","title":{"rendered":"Using Apple&#8217;s New WeatherKit REST API"},"content":{"rendered":"<p>In late March 2020 Apple purchased the <a href=\"https:\/\/darksky.net\/app\">Dark Sky app<\/a>, and along with it the Dark Sky API.  No longer accepting sign ups the Dark Sky API will go offline on <a href=\"https:\/\/darksky.net\/dev\">March 31, 2023<\/a>.  If you&#8217;re a developer that needs a reliable weather API, Apple is now providing <a href=\"https:\/\/developer.apple.com\/weatherkit\/\"><strong>WeatherKit<\/strong><\/a>.  Typical &#8220;Kit&#8221; APIs are only available on Apple devices as libraries, but in this case, WeatherKit <i>does<\/i> have a REST web service available.  Let&#8217;s look at how to use it.<\/p>\n<p>The WeatherKit API does require an Apple developer account and access to the <a href=\"https:\/\/developer.apple.com\">Developer Console<\/a> to configure.  Though the developer account costs $99 a year, that includes 500,000 WeatherKit API calls <em>per month<\/em>.  For me personally that is a much better deal than a service such as a AccuWeather or OpenWeather.<\/p>\n<h2>Provisioning<\/h2>\n<p>To get started with WeatherKit we need to do some provisioning in the Developer Console.  The first step will be to create a <strong>key<\/strong>.<\/p>\n<p><a href=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/certificatesIDsProfiles.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/certificatesIDsProfiles.png\" alt=\"\" width=\"231\" height=\"472\" class=\"aligncenter size-full wp-image-4509\" srcset=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/certificatesIDsProfiles.png 231w, https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/certificatesIDsProfiles-147x300.png 147w\" sizes=\"(max-width: 231px) 100vw, 231px\" \/><\/a><\/p>\n<p>Select <strong>Certificates, Identifiers &amp; Profiles<\/strong> and then <strong>Keys<\/strong>.  Click the blue circle with white cross to add a new key.  We&#8217;ll call the key <em>myweatherapp<\/em>.  Check the box next to <strong>WeatherKit<\/strong>.<\/p>\n<p><a href=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/myweatherapp.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/myweatherapp.png\" alt=\"\" width=\"902\" height=\"649\" class=\"aligncenter size-full wp-image-4511\" srcset=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/myweatherapp.png 902w, https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/myweatherapp-300x216.png 300w, https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/myweatherapp-768x553.png 768w\" sizes=\"(max-width: 902px) 100vw, 902px\" \/><\/a><\/p>\n<p>Click <strong>Continue<\/strong> and then <strong>Register<\/strong> on the <strong>Register a New Key<\/strong> screen.<\/p>\n<p>Make note on this screen that you&#8217;re about to download a new key, and once you&#8217;ve done so you won&#8217;t be able to again.  This key is necessary to access the WeatherKit REST API (you&#8217;ll be signing tokens with it), so keep it in a safe place.<\/p>\n<p><a href=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/keyId.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/keyId.png\" alt=\"\" width=\"1053\" height=\"310\" class=\"aligncenter size-full wp-image-4513\" srcset=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/keyId.png 1053w, https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/keyId-300x88.png 300w, https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/keyId-1024x301.png 1024w, https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/keyId-768x226.png 768w\" sizes=\"(max-width: 1053px) 100vw, 1053px\" \/><\/a><\/p>\n<p>Download your key, and also make note of the <strong>Key ID<\/strong>.  We&#8217;re going to use it later.  Look in your <strong>Downloads<\/strong> folder for <code>AuthKey_KEYID.p8<\/code>.  In our case the filename is <code>AuthKey_9U5ZXJ4Y65.p8<\/code>.<\/p>\n<p>Once you&#8217;ve downloaded your key, click <strong>Done<\/strong>.<\/p>\n<p>Now that we have our key, it&#8217;s time to provision our service identifier.  Click on <strong>Identifiers<\/strong> and once again, the blue circle with white cross.  Choose <strong>Services ID<\/strong> and <strong>Continue<\/strong>.<\/p>\n<p><a href=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/servicesId.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/servicesId.png\" alt=\"\" width=\"719\" height=\"276\" class=\"aligncenter size-full wp-image-4515\" srcset=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/servicesId.png 719w, https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/servicesId-300x115.png 300w\" sizes=\"(max-width: 719px) 100vw, 719px\" \/><\/a><\/p>\n<p>We&#8217;re going to use the reverse domain name notation for the service, i.e., <code>it.iachieved.myweatherapp<\/code>.<\/p>\n<p><a href=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/myWeatherAppServices-1.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/myWeatherAppServices-1.png\" alt=\"\" width=\"1056\" height=\"221\" class=\"aligncenter size-full wp-image-4518\" srcset=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/myWeatherAppServices-1.png 1056w, https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/myWeatherAppServices-1-300x63.png 300w, https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/myWeatherAppServices-1-1024x214.png 1024w, https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/myWeatherAppServices-1-768x161.png 768w\" sizes=\"(max-width: 1056px) 100vw, 1056px\" \/><\/a><\/p>\n<p>Click <strong>Continue<\/strong> and then <strong>Register<\/strong>.<\/p>\n<h2>Preparing the Keys<\/h2>\n<p>The <code>.p8<\/code> file downloaded is a plain text file containing an elliptic curve private key in PKCS#8 format.  It is <em>not<\/em> encrypted.  We&#8217;re going to want the key in PEM format, so let&#8217;s convert it with <code>openssl<\/code>:<\/p>\n<pre class=\"toolbar-overlay:false lang:shell decode:true \" title=\"\" >openssl pkcs8 -nocrypt -in AuthKey_9U5ZXJ4Y65.p8 -out AuthKey_9U5ZXJ4Y65.pem\r\n<\/pre>\n<p><strong>NB<\/strong>:  The option <code>-nocrypt<\/code> is required!<\/p>\n<p>We need the public key component as well for signing JWT tokens, so obtain it with <code>openssl<\/code>:<\/p>\n<pre class=\"toolbar-overlay:false lang:shell decode:true \" title=\"\" >openssl ec -in AuthKey_9U5ZXJ4Y65.pem -pubout &gt; AuthKey_9U5ZXJ4Y65.pub\r\n<\/pre>\n<p>You should now have two files which are the private and public key.<\/p>\n<h2>Creating and Signing a JWT<\/h2>\n<p>Let&#8217;s recall how accessing a REST API with a <a href=\"https:\/\/jwt.io\/introduction\">JSON Web Token<\/a> works.<\/p>\n<p>Apple runs the WeatherKit API service, and in the Developer console you created a key.  Apple kept a copy of the public key which it will use to verify JWT signatures.  Your application is going to construct a JWT and sign it with the private key.  This signed JWT will be presented as a bearer token to the API.  If Apple can validate your signature and that your token contents identify provisioned WeatherKit services, you&#8217;re golden.<\/p>\n<p>We&#8217;ll use <a href=\"https:\/\/jwt.io\">jwt.io<\/a> to create a JWT by hand.<\/p>\n<p>The JWT to access the WeatherKit API must contain the following header elements:<\/p>\n<ul>\n<li>alg &#8211; ES256<\/li>\n<li>kid &#8211; the <strong>Key ID<\/strong> obtained when creating your key<\/li>\n<li>id &#8211; your <a href=\"https:\/\/developer.apple.com\/account\/#!\/membership\/\"><strong>Developer Account Team ID<\/strong><\/a> concatenated with a period, and then your Services Identifier (the reverse domain name)<\/li>\n<li>typ &#8211; JWT<\/li>\n<\/ul>\n<p>The JWT payload must contain the following:<\/p>\n<ul>\n<li>iss &#8211; your Developer Account Team ID<\/li>\n<li>sub &#8211; the Services identifier (the reverse domain name)<\/li>\n<li>iat &#8211; the standard &#8220;issued at&#8221; timestamp in Unix epoch time<\/li>\n<li>exp &#8211; an expiration timestamp in Unix epoch time<\/li>\n<\/ul>\n<p>Here is an example in <a href=\"https:\/\/jwt.io\">jwt.io<\/a>:<\/p>\n<p><a href=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/jwtToken.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/jwtToken.png\" alt=\"\" width=\"521\" height=\"472\" class=\"aligncenter size-full wp-image-4521\" srcset=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/jwtToken.png 521w, https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/jwtToken-300x272.png 300w\" sizes=\"(max-width: 521px) 100vw, 521px\" \/><\/a><\/p>\n<p>When I need a quick copy-paste of the <code>iat<\/code> and <code>exp<\/code> I use this Python one-liner:<\/p>\n<pre class=\"toolbar-overlay:false lang:shell decode:true \" title=\"\" > python -c&#039;import time; n=int(time.time()); print(&quot;\\\\&quot;iat\\\\&quot;: %d,&quot; % n); print(&quot;\\\\&quot;exp\\\\&quot;: %d,&quot; % (n+3600))&#039;\r\n<\/pre>\n<p>Once you&#8217;ve constructed your token&#8217;s contents it&#8217;s time to sign it.  In jwt.io this is accomplished by pasting the public and private key contents into the <strong>Verify Signature<\/strong> inputs.<\/p>\n<p>If successful, you should see something like:<\/p>\n<p><a href=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/bearerToken-1.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/bearerToken-1.png\" alt=\"\" width=\"886\" height=\"749\" class=\"aligncenter size-full wp-image-4523\" srcset=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/bearerToken-1.png 886w, https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/bearerToken-1-300x254.png 300w, https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2022\/09\/bearerToken-1-768x649.png 768w\" sizes=\"(max-width: 886px) 100vw, 886px\" \/><\/a><\/p>\n<p>The encoded and signed token is your <em>bearer<\/em> token that is presented to the WeatherKit API for authentication and authorization.  Note the contents of the private key and the bearer token were blurred, but the JWT contents were not.  You can construct the same contents, but unless they&#8217;re signed with the private key of our provisioned service they&#8217;re unusable.  Thus it is important to keep your private key private!<\/p>\n<h2>Calling the API<\/h2>\n<p>With a bearer token in hand you can make calls to the WeatherKit API!<\/p>\n<p>The first call we&#8217;ll make is to determine what WeatherKit API services are available for the GPS location 32.779167\/-96.808891, which happens to be Dallas, Texas.  In these examples <code>&lt;TOKEN&gt;<\/code> should be replaced with the bearer token.<\/p>\n<pre class=\"toolbar-overlay:false lang:shell decode:true \" title=\"\" >curl &quot;https:\/\/weatherkit.apple.com\/api\/v1\/availability\/32.779167\/-96.808891?country=US&quot; -H &#039;Authorization: Bearer &lt;TOKEN&gt;&#039;\r\n<\/pre>\n<p>This returns:<\/p>\n<pre class=\"toolbar-overlay:false lang:shell decode:true \" title=\"\" >[&quot;currentWeather&quot;,&quot;forecastDaily&quot;,&quot;forecastHourly&quot;,&quot;forecastNextHour&quot;,&quot;weatherAlerts&quot;]\r\n<\/pre>\n<p>In other words, for this location, we can obtain the current weather, daily forecast, hourly forecast, forecast for the <em>next<\/em> hour, and weather alerts.  We&#8217;ll just check the current weather.<\/p>\n<pre class=\"toolbar-overlay:false lang:shell decode:true \" title=\"\" >\r\ncurl &quot;https:\/\/weatherkit.apple.com\/api\/v1\/weather\/en_US\/32.779167\/-96.808891?dataSets=currentWeather&quot; \\\r\n     -H &#039;Authorization: Bearer &lt;TOKEN&gt;&#039;\r\n<\/pre>\n<p>A few things to note here:<\/p>\n<ul>\n<li>the route changed to <code>\/api\/v1\/weather\/<\/code><\/li>\n<li>the inclusion of a locale code (e.g., <code>en_US<\/code>)<\/li>\n<li>the query parameter <code>dataSets<\/code><\/li>\n<\/ul>\n<p>The result of the call:<\/p>\n<pre class=\"toolbar-overlay:false lang:json decode:true \" title=\"\" >{&quot;currentWeather&quot;:{&quot;name&quot;:&quot;CurrentWeather&quot;,&quot;metadata&quot;:{&quot;attributionURL&quot;:&quot;https:\/\/weatherkit.apple.com\/legal-attribution.html&quot;,&quot;expireTime&quot;:&quot;2022-09-11T14:57:09Z&quot;,&quot;latitude&quot;:32.779,&quot;longitude&quot;:-96.809,&quot;readTime&quot;:&quot;2022-09-11T14:52:09Z&quot;,&quot;reportedTime&quot;:&quot;2022-09-11T14:52:09Z&quot;,&quot;units&quot;:&quot;m&quot;,&quot;version&quot;:1},&quot;asOf&quot;:&quot;2022-09-11T14:52:09Z&quot;,&quot;cloudCover&quot;:0.41,&quot;conditionCode&quot;:&quot;PartlyCloudy&quot;,&quot;daylight&quot;:true,&quot;humidity&quot;:0.70,&quot;precipitationIntensity&quot;:0.00,&quot;pressure&quot;:1021.51,&quot;pressureTrend&quot;:&quot;rising&quot;,&quot;temperature&quot;:22.17,&quot;temperatureApparent&quot;:22.72,&quot;temperatureDewPoint&quot;:16.52,&quot;uvIndex&quot;:3,&quot;visibility&quot;:26705.41,&quot;windDirection&quot;:348,&quot;windGust&quot;:30.99,&quot;windSpeed&quot;:18.18}}\r\n<\/pre>\n<p>The units are returned in metric, and annoyingly the condition code will need to be mapped to make it user friendly (&#8220;Partly Cloudy&#8221; instead of &#8220;PartlyCloudy&#8221;).<\/p>\n<h2>A C++ JWT Signing Implementation<\/h2>\n<p>We used the jwt.io site to quickly cobble together a usable bearer token, but if you&#8217;re building an actual application to make requests to the WeatherKit API you&#8217;re going to want to implement JWT signing in code.<\/p>\n<p>Here&#8217;s an example in C++ utilizing the <a href=\"https:\/\/github.com\/Thalhammer\/jwt-cpp\">jwt-cpp<\/a> header-only library.<\/p>\n<pre class=\"toolbar-overlay:false lang:c++ decode:true \" title=\"C++ JWT\" >#include &lt;fstream&gt;\r\n#include &lt;sstream&gt;\r\n#include &lt;string&gt;\r\n\r\n#include &lt;jwt-cpp\/jwt.h&gt;\r\n\r\nint main(void) {\r\n\r\n  \/\/ Read in private key\r\n  std::ifstream     f(\"priv.key\");\r\n  std::stringstream buf;\r\n  buf &lt;&lt; f.rdbuf();\r\n\r\n  std::string priv_key = buf.str();\r\n\r\n  \/\/ Read in public key\r\n  f.close(); f.open(\"pub.key\");\r\n  buf &lt;&lt; f.rdbuf();\r\n  std::string pub_key = buf.str();\r\n\r\n  auto token = jwt::create()\r\n                .set_issuer(\"5367BG94QP\")\r\n                .set_type(\"JWT\")\r\n                .set_key_id(\"9U5ZXJ4Y65\")\r\n                .set_header_claim(\"id\",\r\n                  jwt::claim(std::string(\"5367BG94QP.it.iachieved.myweatherapp\")))\r\n                .set_subject(\"it.iachieved.myweatherapp\")\r\n                .set_issued_at(std::chrono::system_clock::now())\r\n                .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds {3600})\r\n                .sign(jwt::algorithm::es256(pub_key, priv_key));\r\n\r\n  std::cout &lt;&lt; \"Bearer \" &lt;&lt; token &lt;&lt; std::endl;\r\n\r\n  return 0;\r\n}\r\n<\/pre>\n<h2>WeatherKit REST API Documentation<\/h2>\n<p>The complete REST API documentation for WeatherKit can be found at <a href=\"https:\/\/developer.apple.com\/documentation\/weatherkitrestapi\">https:\/\/developer.apple.com\/documentation\/weatherkitrestapi<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In late March 2020 Apple purchased the Dark Sky app, and along with it the Dark Sky API. No longer accepting sign ups the Dark Sky API will go offline on March 31, 2023. If you&#8217;re a developer that needs a reliable weather API, Apple is now providing WeatherKit. Typical &#8220;Kit&#8221; APIs are only available [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11,22],"tags":[114,115],"class_list":["post-4508","post","type-post","status-publish","format-standard","hentry","category-apple","category-hacking","tag-weatherkit","tag-weatherkit-rest-api"],"_links":{"self":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/4508"}],"collection":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/comments?post=4508"}],"version-history":[{"count":27,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/4508\/revisions"}],"predecessor-version":[{"id":5022,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/4508\/revisions\/5022"}],"wp:attachment":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/media?parent=4508"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/categories?post=4508"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/tags?post=4508"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}