Elixir client for the Apple Maps Server API.

Features

  • ES256 JWT signing and /v1/token access-token exchange
  • Supervised token cache (no per-call token minting)
  • Place search, autocomplete, forward/reverse geocoding, directions, ETAs
  • Optional response decoding into typed structs (Place, Coordinate)
  • Typed errors with status-aware messages

Installation

def deps do
  [
    {:apple_maps_server, "~> 0.1.0"}
  ]
end

Configuration

Get a Maps identifier and private key in your Apple Developer account (Apple's walkthrough), then:

config :apple_maps_server,
  maps_id:          System.get_env("APPLE_MAPS_ID"),
  team_id:          System.get_env("APPLE_TEAM_ID"),
  key_id:           System.get_env("APPLE_MAPS_KEY_ID"),
  private_key:      System.get_env("APPLE_MAPS_PRIVATE_KEY"),
  # or: private_key_path: "/path/to/AuthKey_XXXX.p8"
  base_url:         "https://maps-api.apple.com",
  token_ttl_seconds: 1800

Every public function also accepts per-call opts that override the application config — useful in tests or for multi-tenant deployments. Passing any config key in the opts bypasses the token cache for that call.

Usage

{:ok, res} = AppleMapsServer.search("coffee")
{:ok, res} = AppleMapsServer.search_autocomplete("cof")
{:ok, res} = AppleMapsServer.geocode("1 Infinite Loop, Cupertino, CA")
{:ok, res} = AppleMapsServer.reverse_geocode(%{latitude: 37.3318, longitude: -122.0312})
{:ok, res} = AppleMapsServer.directions("San Francisco, CA", "Cupertino, CA")
{:ok, res} = AppleMapsServer.etas("37.33,-122.03", ["37.77,-122.41", "37.80,-122.27"])
{:ok, tok} = AppleMapsServer.token()

Typed responses

Opt in per call with decode: true:

{:ok, %{"results" => [%AppleMapsServer.Place{} = place | _]}} =
  AppleMapsServer.geocode("1 Infinite Loop, Cupertino, CA", decode: true)

place.coordinate  #=> %AppleMapsServer.Coordinate{latitude: 37.33, longitude: -122.03}

Errors

ShapeMeaning
{:error, %AppleMapsServer.Error{status: 401}}Token rejected — check team/key IDs and .p8
{:error, %AppleMapsServer.Error{status: 403}}Maps capability not enabled for the key
{:error, %AppleMapsServer.Error{status: 422}}Invalid request parameters
{:error, %AppleMapsServer.Error{status: 429}}Rate limited — back off
{:error, {:missing_config, :team_id}}Required config not set
{:error, {:token_generation_failed, reason}}JOSE could not sign (usually malformed .p8)
{:error, {:transport_error, reason}}Network / connection failure

Integration testing

Integration tests hit the real Apple API and are excluded by default:

APPLE_MAPS_ID=... \
APPLE_TEAM_ID=... \
APPLE_MAPS_KEY_ID=... \
APPLE_MAPS_PRIVATE_KEY_PATH=/path/to/AuthKey_XXXX.p8 \
mix test --only integration

Troubleshooting

  • 401 unauthorized — JWT rejected. Verify team_id, key_id, and that private_key is the .p8 PEM (contains BEGIN PRIVATE KEY). Make sure the scope: "server_api" claim is present (this library sets it automatically).
  • 403 forbidden — Maps capability not enabled for the key, or the Maps ID doesn't match the key.
  • :token_generation_failed — Most often an invalid PEM. .p8 files already include PEM headers; do not re-wrap them.
  • 429 rate limited — Apple throttles aggressively; implement retry/backoff in your caller.

License

MIT — see LICENSE.