Developer toolkit · Norway
The machine gate,
made passable.
Maskinporten is the OAuth2 machine-to-machine gate in front of ~50 Norwegian public-sector APIs. This toolkit gets you through it: a TypeScript client, a credential-free mock, and a wizard for the scopes you need.
Why
Getting a token shouldn't be a rite of passage
To call a Maskinporten-protected API you build a JWT grant with exactly the right claims, sign it, and exchange it — and for Altinn, exchange it again. Java and .NET have libraries for this. Node/TypeScript didn't, so every team hand-rolls the same glue — and testing it means real organisation credentials against a test environment.
Maskinporten Tools removes that: one call for a cached token, a local mock so you build and CI-test with zero credentials, and a wizard that tells you which scopes and Altinn resources your use-case needs.
Toolkit
Three tools, one gate
maskinporten
The client. One call → a cached, auto-renewing token. Altinn token exchange and systembruker built in.
maskinporten-mock
A credential-free mock of the token endpoint. Develop and CI-test offline — the same mock this page talks to.
maskinporten-wizard
A CLI that does what a page can't: init scaffolds a keypair + .env;
doctor runs a real token request and decodes the failure.
Client
A token in a few lines
Describe your credentials once, then ask for a token. JWT assertions, caching, renewal,
and the two-step Altinn exchange are handled for you. Every tab is real
maskinporten code.
import { createMaskinportenClient } from 'maskinporten';
// Describe your credentials once.
const client = createMaskinportenClient({
env: 'test',
clientId: process.env.MASKINPORTEN_CLIENT_ID!,
scope: 'skatteetaten:formueinntekt/skattemelding',
key: {
pem: process.env.MASKINPORTEN_PRIVATE_KEY!,
kid: process.env.MASKINPORTEN_KID!,
},
});
// One call. The token is cached and auto-renewed for you.
const token = await client.getToken();
// getToken() caches and renews under the hood,
// so it is safe to call on every request.
const token = await client.getToken();
// Call the API your scope was granted for.
const response = await fetch('https://api.example.no/v1/resource', {
headers: { Authorization: `Bearer ${token}` },
});
const data = await response.json();
// Add the organisation you act on behalf of...
const client = createMaskinportenClient({
env: 'test',
clientId: process.env.MASKINPORTEN_CLIENT_ID!,
scope: 'altinn:instances.read',
key: {
pem: process.env.MASKINPORTEN_PRIVATE_KEY!,
kid: process.env.MASKINPORTEN_KID!,
},
systemUserOrg: '991825827',
});
// ...and the two-step Maskinporten to Altinn exchange,
// with its own caching, is a single call.
const altinnToken = await client.exchangeToAltinnToken();
// Terminal: npx maskinporten-mock (listens on :6969)
import { createMaskinportenClient } from 'maskinporten';
const client = createMaskinportenClient({
env: 'test',
clientId: 'local-dev',
scope: 'demo:scope',
key: { pem: process.env.DEV_PRIVATE_KEY!, kid: 'dev' },
// Point the client at the local mock. That is the only change.
tokenEndpoint: 'http://localhost:6969/token',
});
// Real JWT-bearer flow, fake gate. No credentials, offline, CI-ready.
const token = await client.getToken();
import { createMaskinportenClient, MaskinportenError } from 'maskinporten';
try {
const token = await client.getToken();
} catch (error) {
if (error instanceof MaskinportenError) {
// Opaque codes like AUTH-00004 arrive decoded and readable.
console.error(error.code, error.description);
}
throw error;
}
Wizard
Which scopes do I need?
Pick an integration goal. The wizard lists the exact Maskinporten scopes, Altinn resource URNs, which authority grants them, and the registration steps to do first.
Playground
Poke the live mock
It behaves like the real Maskinporten token endpoint — discovery, JWKS, and the JWT-bearer grant — without any of the setup. Every call below hits the hosted mock from your browser.
Choose a call on the left. Responses show here, exactly as the mock returns them.
Docs
Read before you register
Calling real Maskinporten needs a Norwegian organisation number, a signing key, a registered client, and pre-allocated scopes. These cover the whole path.