How do I authenticate against Norwegian government APIs?
With a Maskinporten machine-to-machine access token, obtained by signing a short-lived JWT client assertion with a private key anchored on an enterprise certificate (virksomhetssertifikat, issued by Buypass or Commfides to a Brønnøysund-registered entity). You post that assertion to the token endpoint and receive a scoped token you present as a Bearer credential to the agency API. Apier holds the certificate, signs the assertions and manages the token lifecycle behind one Bearer key of its own, so your code never performs the exchange.
Do I need a virksomhetssertifikat for Altinn 3?
For a direct integration, yes. A virksomhetssertifikat is an enterprise certificate issued to a legal entity registered in Brønnøysund, sold by Buypass and Commfides. The distinction that catches people out is that it identifies the organisation rather than a person: it is not your BankID, and it is not tied to whoever happens to be the developer. Ordering one involves an identity check on the organisation, so treat the lead time as a project dependency rather than an afternoon task.
The certificate anchors a key pair. You register the public half with Digdir and keep the private half on your own infrastructure, where it signs the assertions described below. Nothing about the certificate is sent to the agency API on a normal call, which surprises people who expect mutual TLS: the certificate proves who you are to the token service, and the token proves it to everyone else.
Through a broker, none of that is yours. Apier holds the certificate, registers the key and signs the assertions, and your application authenticates with an ordinary Bearer key. The Norwegian-language Maskinporten API overview covers the brokered setup, and the Maskinporten guide for developers walks the direct exchange end to end, including the failure modes that are easiest to misdiagnose.
How does the JWT client-credentials exchange work?
In two calls. First you build a JSON Web Token asserting who you are and what you want: the issuer claim carries your Maskinporten client id, the audience claim names the Maskinporten environment you are talking to, a scope claim lists the access you are asking for, and the issued-at and expiry claims keep the assertion valid for a short window. You sign it with the private key registered against your client. That signed blob is the assertion.
Second, you post the assertion to the token endpoint using the JWT bearer grant type defined by RFC 7523. Maskinporten verifies the signature against the public key you registered, checks that the scopes you asked for are ones your client has been granted, and returns an access token together with its expiry and the scopes actually issued. Read that scope list rather than assuming you got what you requested, because a partially granted response is not an error and will otherwise fail later at the agency API.
From there the access token is an ordinary Bearer credential on the agency call. Two failure modes account for most lost debugging time. Clock skew invalidates assertions silently, so keep your signing host in sync with real time and do not set the assertion window wider to paper over drift. And scopes are granted per client by the agency that owns the data, so a correct signature with an ungranted scope fails at the token endpoint rather than at the API, which is a different fix in a different console.
How should tokens be cached and rotated?
Cache the access token, and key the cache carefully. Because tokens are short-lived, requesting one per API call adds a round trip to every request and loads the token endpoint for no benefit. The usual shape is an in-process cache holding the token until shortly before expiry, with a small safety margin so a request in flight does not arrive with a token that has just aged out.
The cache key is where this gets dangerous. A plain scope-level token can safely be shared across calls, but a token issued for acting on behalf of a customer is bound to that one organisation. If both land in a cache keyed only on scope, a request about one customer can be served a token minted for another. That is a cross-organisation data exposure, not a performance bug, so the key for an on-behalf-of token must include the organisation it was issued for.
Key and certificate rotation runs on a slower clock and needs deliberate overlap: register the new public key before retiring the old one, so assertions signed with either verify during the changeover. The repository's Maskinporten key rotation guide covers the sequencing. Through a broker this schedule is not yours to run, which is most of the operational argument for brokering in the first place.
| Responsibility | Self-managed | Brokered through Apier |
|---|---|---|
| Enterprise certificate | You order a virksomhetssertifikat from Buypass or Commfides and renew it before it lapses. | Held by Apier. You never obtain, store or renew a certificate. |
| Key registration | You register the public key with Digdir and keep the private key on infrastructure you control. | Held by Apier. Your application stores one Bearer key instead of a private key. |
| JWT assertion | You build and sign an assertion per token request, with correct claims and a synchronised clock. | Not in your code path at all. |
| Scope grants | Requested per client from the agency that owns the data, then approved on their schedule. | Held against Apier's client. Your key carries Apier scopes such as read:brreg and read:altinn. |
| Token cache and rotation | You cache per scope and per organisation, refresh before expiry, and stage key rollovers with overlap. | Managed on Apier's side. Your key is long-lived and rotates on your own schedule. |
| Per-customer delegation | Granted by each customer to your registered system in Altinn. | Still granted by the customer. A broker cannot remove this leg. |
Make the first call
The sandbox request below needs no certificate and no token exchange. The TypeScript snippet is the production equivalent, with the 401 branch written the way it should be handled.
# Zero-auth sandbox: no certificate, no token exchange, no key.
curl -s https://www.apier.no/api/v1/sandbox/public/company/999999999/summary// Production. The token exchange happens on Apier's side.
const res = await fetch(
"https://www.apier.no/api/v1/company/999999999/summary",
{ headers: { Authorization: `Bearer ${process.env.APIER_API_KEY}` } },
);
if (!res.ok) {
// 401 is AUTH_MISSING or AUTH_INVALID: fix the key rather than retrying.
// Every non-2xx answers in this same envelope, so one branch covers them.
const { error_code, explanation } = await res.json();
throw new Error(`${error_code}: ${explanation.summary}`);
}
const { data, _meta } = await res.json();
console.log(data.name, _meta.data_freshness);Frequently asked questions
- Do I need a virksomhetssertifikat to call Altinn 3?
- For a direct integration, yes. Maskinporten authenticates a client by a signed JWT assertion, and the key behind that assertion is anchored on an enterprise certificate issued to a Brønnøysund-registered entity by Buypass or Commfides. It identifies the organisation, not a person. Through a broker that already holds one, the certificate belongs to the broker rather than to you.
- Is Maskinporten the same thing as an API key?
- No. An API key is a static secret you send as-is. A Maskinporten credential is an asymmetric key pair: you sign a short-lived JWT assertion with the private key, post it to the token endpoint, and receive a scoped access token in return. The private key never leaves your infrastructure, and the token you actually send expires in minutes.
- How long does a Maskinporten access token last?
- Minutes rather than hours, which is why the exchange is a loop and not a setup step. Cache the token until shortly before its expiry and re-sign when it ages out. Do not request a fresh token per API call: that adds a round trip to every request and puts avoidable load on the token endpoint.
- Can I cache one token and reuse it for every customer?
- Only for scope-level calls that are not tied to a specific organisation. A token issued for acting on behalf of a customer is bound to that one organisation, so a cache keyed on scope alone will hand a token minted for one customer to a request about another. The cache key must include the organisation, not just the scope.
- What does Apier take off my plate here?
- The certificate, the JWK registration, the assertion signing, the token cache and the rotation schedule. Your application sends one Bearer key and never handles a government token. What a broker cannot remove is the per-customer delegation, because that encodes a decision only the customer is entitled to make.