# Authentication Source: https://docs.native.no/authentication > **Preview.** These APIs are not live yet. This page describes the planned interface; names and fields can change before access opens. One login for the Norn API and the Native API: a client ID and secret for your backend, exchanged for an access token that lasts at most an hour. > **Not live yet.** Machine-to-machine access is still being built. Credentials are issued when your access opens. Until then, the Native MCP server only accepts people signing in through an assistant, as on the [Claude connector](https://native.no/claude-connector) page. ## One login for both APIs When your access opens, Native gives your team one machine-to-machine application, with a `client_id` and a `client_secret`. The same pair signs you in to the Norn API and the Native API. Your backend trades it for a short-lived access token and sends that token with every call. Keep the secret on your server. Never put it in a browser, a mobile app or a repository. If it leaks, write to [hei@native.no](mailto:hei@native.no) and we will replace it. ## Get an access token Use the OAuth client-credentials grant. Name the API you want a token for in `audience`: | API | `audience` | `scope` | Status | | --- | --- | --- | --- | | [Native API](https://docs.native.no/native-api) | `https://mcp.native.no/mcp` | `mcp:read mcp:write` | In development | | [Norn API](https://docs.native.no/norn) | `https://api.native.no/v1` | Set when your access opens | Planned | cURL: ```bash curl -X POST https://auth.native.no/oauth/token \ -d grant_type=client_credentials \ -d client_id="$NATIVE_CLIENT_ID" \ -d client_secret="$NATIVE_CLIENT_SECRET" \ -d audience=https://mcp.native.no/mcp \ -d scope="mcp:read mcp:write" ``` TypeScript: ```ts const TOKEN_URL = 'https://auth.native.no/oauth/token' // Cache one token per API and fetch a new one a minute before it runs out. // Calls that arrive while a token is on its way share that request. const cache = new Map() const pending = new Map>() export async function accessToken(audience: string, scope: string): Promise { const key = `${audience} ${scope}` const hit = cache.get(key) if (hit && hit.expiresAt - Date.now() > 60_000) return hit.token let request = pending.get(key) if (!request) { request = requestToken(audience, scope) .then((entry) => { cache.set(key, entry) return entry.token }) .finally(() => pending.delete(key)) pending.set(key, request) } return request } async function requestToken(audience: string, scope: string) { const res = await fetch(TOKEN_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ grant_type: 'client_credentials', client_id: process.env.NATIVE_CLIENT_ID, client_secret: process.env.NATIVE_CLIENT_SECRET, audience, scope, }), }) if (!res.ok) throw new Error(`Token request failed: ${res.status}`) const body = (await res.json()) as { access_token: string; expires_in: number } return { token: body.access_token, expiresAt: Date.now() + body.expires_in * 1000 } } const token = await accessToken('https://mcp.native.no/mcp', 'mcp:read mcp:write') ``` Python: ```python import os import requests res = requests.post( "https://auth.native.no/oauth/token", data={ "grant_type": "client_credentials", "client_id": os.environ["NATIVE_CLIENT_ID"], "client_secret": os.environ["NATIVE_CLIENT_SECRET"], "audience": "https://mcp.native.no/mcp", "scope": "mcp:read mcp:write", }, timeout=30, ) res.raise_for_status() token = res.json()["access_token"] ``` Response: ```json { "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…", "token_type": "Bearer", "expires_in": 3600, "scope": "mcp:read mcp:write" } ``` - The audience must match the API exactly. A token for one API does not work on the other: fetch one token per API you call. - A token lasts at most one hour, and the Native API refuses a token that would last longer. Cache it and fetch a new one shortly before `expires_in` runs out. - Every Native API tool needs `mcp:read`. Tools that change something also need `mcp:write`. ## Sandbox When your access opens, you can also ask for credentials on Native’s development environment, for trying calls without touching real customers. They use the same flow, with a token endpoint and an MCP address of their own that we send with them. ## Use the token Send the token as a bearer token on every request: HTTP: ```http Authorization: Bearer ``` The Native API is MCP over Streamable HTTP, so the token goes on the transport when you create the MCP client. Make a new client when you fetch a new token. [The Native API overview](https://docs.native.no/native-api) shows the client. ## When a token is refused | What you get | Why | What to do | | --- | --- | --- | | HTTP `401` with a `WWW-Authenticate` challenge | The token is missing, expired or not valid for this API. | Fetch a new token for the right `audience`. | | HTTP `403`, `insufficient_scope` | The token lacks `mcp:write` for a tool that changes something. | Ask for `mcp:read mcp:write` in `scope`. | | Tool error `partner_not_found` | The token is valid, but Native has no partner set up for your client ID. | Write to [hei@native.no](mailto:hei@native.no). |