Authentication

Every request to the Mountaya API requires a publishable key. For stronger security, enable session tokens — short-lived credentials that prevent key theft and replay attacks.

What each key can access is governed by safety rules, configured at the organization level.

API keys

Mountaya uses two types of API keys. You can create and manage them from your dashboard.

Publishable keys (pk_) identify your organization in every request. They are safe to expose in frontend code — they can only read data within the boundaries defined by your safety rules. Include them in the X-API-Key header for API calls, or as the publishable_key query parameter for embedded iframes. Each key has scopes that control which products it can access: embedding, tiles, data.

Secret keys (sk_) are used exclusively to create session tokens via the POST /v1/sessions endpoint. They must never appear in client-side code, public repositories, or browser network requests.

You can create multiple keys of each type, name them for easy identification, and revoke them at any time. Revocation takes effect immediately.

Session tokens

Session tokens add a layer of security on top of publishable keys. They are short-lived (5 minutes), cryptographically signed, and tied to a specific publishable key.

A publishable key is public by nature — visible in iframe URLs and browser network requests. Without session tokens, anyone who copies the key and spoofs the domain can make requests on behalf of your organization. Session tokens prevent this by requiring a server-to-server exchange that only your backend can perform.

Organizations without a backend (e.g. static sites that only embed iframes) may disable this rule. Domain validation alone provides basic protection, but it can be bypassed by determined attackers.

Organizations that use the Data Platform must always provide a session token, regardless of this setting.

How it works

Your backend exchanges a secret key for a short-lived session token, then passes it to your frontend. The frontend includes it in every request.

Your frontend        Your backend                  Mountaya API
    |                     |                             |
    |  GET /token         |                             |
    |  -----------------> |                             |
    |                     |  POST /v1/sessions          |
    |                     |  X-API-Key: sk_...          |
    |                     |  { "publishable_key": "pk_..." }
    |                     |  -------------------------> |
    |                     |                             |
    |                     |  201 { token, expires_at }  |
    |                     |  <------------------------- |
    |  { token, expiresAt }                             |
    |  <----------------- |                             |
    |                     |                             |
    |  Use token in API   |                             |
    |                     |                             |
    |  ... 4 min later,   |                             |
    |  repeat from top    |                             |

Step 1: Add a token endpoint to your backend

Create an endpoint on your server that exchanges your secret key for a session token. This endpoint should be protected by your own authentication so that only your authorized users can request tokens.

import express from "express";

const app = express();

const MOUNTAYA_SECRET_KEY = process.env.MOUNTAYA_SECRET_KEY;
const MOUNTAYA_PUBLISHABLE_KEY = process.env.MOUNTAYA_PUBLISHABLE_KEY;

async function createSessionToken() {
  const response = await fetch("https://internal.mountaya.com/v1/sessions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": MOUNTAYA_SECRET_KEY,
    },
    body: JSON.stringify({ publishable_key: MOUNTAYA_PUBLISHABLE_KEY }),
  });

  if (!response.ok) {
    throw new Error(`Mountaya API error: ${response.status}`);
  }

  const { data } = await response.json();
  return { token: data.token, expiresAt: data.expires_at };
}

app.get("/mountaya/token", async (req, res) => {
  const session = await createSessionToken();
  res.json(session);
});

The API responds with a token and its expiration time:

{
  "status": 201,
  "data": {
    "token": "sess_eyJvcm...",
    "expires_at": "2026-03-03T12:05:00Z"
  }
}

Step 2: Fetch and refresh the token

Session tokens expire after 5 minutes. Request a fresh token every 4 minutes to maintain a 1-minute safety margin for network latency and clock drift.

const REFRESH_INTERVAL_MS = 4 * 60 * 1000; // 4 minutes

let sessionToken = null;

async function refreshToken() {
  try {
    const res = await fetch("/mountaya/token");
    if (!res.ok) throw new Error(`Token fetch failed: ${res.status}`);
    const data = await res.json();
    sessionToken = data.token;
  } catch (err) {
    console.error("Failed to refresh Mountaya session token", err);
  }
}

// 1. Fetch a token immediately on page load.
await refreshToken();

// 2. Schedule a refresh every 4 minutes.
const intervalId = setInterval(refreshToken, REFRESH_INTERVAL_MS);

// 3. Clean up when the page unmounts (SPA cleanup).
// clearInterval(intervalId);

No need to persist the token to localStorage or cookies. If the user refreshes the page, the script fetches a new one immediately.

Step 3: Use the token

API requests — include the token in the X-Session-Token header alongside the publishable key in X-API-Key:

const response = await fetch("https://data.mountaya.com/graphql", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "pk_your_publishable_key",
    "X-Session-Token": sessionToken,
  },
  body: JSON.stringify({
    query: `{
      directions(input: {
        activityId: "hiking",
        waypoints: [[6.869, 45.923], [6.862, 45.832]]
      }) {
        routes { distance duration }
      }
    }`,
  }),
});

Embedded iframes — pass the token as a query parameter and update the src every time the token is refreshed:

<iframe
  id="mountaya-embed"
  src="https://app.mountaya.com/studio/embed
    ?publishable_key=pk_your_publishable_key
    &session_token=sess_eyJvcm...
    &collection_id=uuid
    &route_id=uuid">
</iframe>
function updateIframeToken(newToken) {
  const iframe = document.getElementById("mountaya-embed");
  const url = new URL(iframe.src);
  url.searchParams.set("session_token", newToken);
  iframe.src = url.toString();
}

Step 4: Handle expired tokens

If the user's device sleeps or loses connectivity, the refresh interval may not fire and the token can expire. Wrap your API calls to retry once on 401:

async function fetchMountaya(url, options = {}) {
  const attempt = () =>
    fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        "X-API-Key": "pk_your_publishable_key",
        "X-Session-Token": sessionToken,
      },
    });

  const response = await attempt();

  if (response.status === 401) {
    await refreshToken();
    return attempt();
  }

  return response;
}

Test your integration

Verify the full flow from your terminal:

# 1. Create a session token.
curl -s -X POST https://internal.mountaya.com/v1/sessions \
  -H "Content-Type: application/json" \
  -H "X-API-Key: sk_your_secret_key" \
  -d '{ "publishable_key": "pk_your_publishable_key" }'
# -> { "status": 201, "data": { "token": "sess_...", "expires_at": "..." } }

# 2. Use the token in a Data Platform request.
curl -s -X POST https://data.mountaya.com/graphql \
  -H "Content-Type: application/json" \
  -H "X-API-Key: pk_your_publishable_key" \
  -H "X-Session-Token: sess_PASTE_TOKEN_HERE" \
  -d '{ "query": "{ directions(input: { activityId: \"hiking\", waypoints: [[6.869, 45.923], [6.862, 45.832]] }) { routes { distance } } }" }'

If the second request returns data, your keys and session flow are working. If you get a 401, verify that both keys belong to the same organization and that the token has not expired.

Best practices

  • Never expose secret keys in client-side code. This includes JavaScript bundles, HTML source, mobile app binaries, and public repositories. If a secret key is compromised, revoke it immediately and create a new one.
  • Rotate keys periodically. Create a new key, update your systems, then revoke the old one. You can have multiple active keys at any time.
  • Use scoped publishable keys. Enable only the scopes your use case requires (embedding, tiles, data). Fewer scopes means less exposure if a key is misused.
  • Enable require_session_token. This is the strongest protection against key theft and the only way to prevent replay attacks with a copied publishable key.