Why does my Altinn system user request return 403 or 500?
By Antony Richard Grov, founder of Apier
Almost always because of who holds what, not because of what you sent. A 403 while approving a system user request usually means the approver does not hold, or cannot delegate, one of the access packages the request names. A 403 after a successful delegation, when the target is Altinn Apps or another API that requires the exchanged Altinn token, usually means a raw Maskinporten token was sent unexchanged; APIs documented to accept system user tokens directly, such as Dialogporten, are not this failure. And some of the 500s in this flow behave like rejections wearing the wrong status code, so treat them as authority problems before treating them as outages.
Why does approving a system user request fail with 403?
Because approval is not a signature, it is a delegation. When your customer clicks approve on the request your system created, Altinn delegates the named access packages to the system user, and the person doing that must hold the authority to delegate each one. The registry roles cover the common cases: a managing director or another key role can delegate most packages. The sensitive packages are the trap, since they have no pre-assigned roles, and only the business's hovedadministrator can hand them out.
The failure is confusing in practice because the person approving is usually senior, and seniority is not the check. The check runs per package, so one package in the request that the approver cannot delegate is enough for a rejection, even when the other four would have gone through. Integrators also observe the delegation check rejecting approvals with an error code rather than a readable message, which turns a rights question into what looks like a technical fault.
The fix is to make the request approvable before it is sent. Ask for the narrowest package set the integration actually needs, check which packages carry pre-assigned registry roles and which are administrator-only, and tell the customer who at their end has to do the approving. When a mixed request keeps failing, split it: a request for the uncontroversial packages that goes through today beats one perfect request that sits rejected.
What do the 500s in this flow actually mean?
Officially, a 500 is a server fault and a 403 is a refusal. In this flow the line is blurrier than the status codes suggest: integrators observe server errors at points where the underlying condition is a refusal, for example when an access package delegation is rejected during approval. Nothing about that is documented behaviour to rely on; it is simply what the flow has been observed to do, which changes how you should debug it.
The practical rule: when a 500 appears at an authorisation-shaped step, exhaust the 403 hypotheses before the outage hypotheses. Re-check the approver's authority, the requested packages and the state of the delegation as if the answer had been an explicit refusal, because a blind retry loop against a refusal-shaped 500 burns time and produces a queue of identical failures. If the authority checks out and the error persists, then treat it as an incident.
Two smaller symptoms belong to the same observed cluster. Listing your vendor requests can misbehave at pagination boundaries, so harden the client against a page that does not arrive rather than assuming the list is complete. And a repeated create for a request that already exists has been observed to answer without the confirmUrl the first response carried, so persist that URL when you first receive it instead of re-deriving it from a repeat call.
| Symptom | Likely cause | Fix |
|---|---|---|
| 403 approving the request | The approver does not hold, or cannot delegate, one of the requested access packages. | Have the right person approve, or narrow the package set. Administrator-only packages need the hovedadministrator. |
| Delegation check rejects the approval | The per-package authority check fails for at least one named package (observed with opaque error codes). | Audit the request package by package against the approver's actual authority; split mixed requests. |
| 500 where a refusal makes sense (observed) | A rejected package delegation or similar refusal surfacing with a server-error status. | Debug it as a 403 first: verify authority and packages before retrying or declaring an outage. |
| 403 on the API call after approval | The target API requires the exchanged Altinn token (Altinn Apps and the classic platform APIs) and received a raw Maskinporten token. APIs documented to accept system user tokens directly are unaffected. | Exchange the token first and present the Altinn token on app and platform calls. |
| Vendor request list breaks mid-listing (observed) | Pagination boundaries misbehaving on the list endpoint. | Treat a missing page as a normal condition: bound retries, and reconcile against your own stored request ids. |
| confirmUrl missing on a repeat (observed) | A repeated create for an existing request answering without the URL the first response carried. | Persist confirmUrl from the first response and hand the stored value to the customer. |
How do I make this whole error class boring?
Separate the three questions the flow keeps collapsing into one request: has authority been asked for, has a human granted it, and is it active right now. Handled as one step, every failure looks the same from the outside. Handled as three, each failure names its own cause, and the debugging table above shrinks to whichever step you are actually on. That separation is a design choice you can make in your own client regardless of what you integrate through.
Apier's part in that separation is the first question only. GET /api/v1/company/{org}/authority returns the customer's registered signing rule, as signaturrett and prokura from Brønnøysund with source and date. That rule records who may commit the company; it does not say who may approve a system user request, which the person's rights in Altinn decide. Whether authority has been asked for, and whether it is active right now, are questions for your own system register client and the Altinn authorisation API; Apier does not read or broker them.
The concepts underneath, what a system user is, why the customer owns the grant, and how revocation behaves, are owned by the system user setup walkthrough, and the token half of the story by the token exchange guide. This page stays what it is: the map from symptom to cause when the flow says no.
Make the first call
The sandbox call answers who may act for a company at all, with no key. The TypeScript sample is the production read of the same question: the registered signatories for a company, which tells you who can approve a system user request before you send one.
# Zero-auth sandbox: who may act for a company, from the registry.
curl -s https://www.apier.no/api/v1/sandbox/public/company/999999999/authority// Who may approve: the registered signatories, from Brønnøysund.
// Send the approval link to a person whose authority covers every package.
// Replace {org} with a real organisasjonsnummer; 999999999 exists only in the sandbox.
const res = await fetch("https://www.apier.no/api/v1/company/{org}/authority", {
headers: { Authorization: `Bearer ${process.env.APIER_API_KEY}` },
});
if (!res.ok) {
// Every non-2xx answers the same structured envelope.
const { error_code, explanation } = await res.json();
throw new Error(`${error_code}: ${explanation.summary}`);
}
const { data, _meta } = await res.json();
// "sole" | "joint" | "by_role" | "prokura_only" | "no_authority" | "unknown"
console.log(data.classification, data.summary, _meta.data_source);Frequently asked questions
- Who can approve a system user request?
- Someone whose own authority covers what they are approving. The approval delegates the requested access packages to your system user, and a person cannot delegate a package they do not hold the right to delegate. Registry roles such as the managing director carry that authority for most packages; the sensitive packages have no pre-assigned roles at all, so only the business's hovedadministrator can approve those.
- Why did approval fail even though the approver is the CEO?
- Because the check is per package, not per person. An approval that bundles several packages needs the approver to be able to delegate every one of them, so a single sensitive package in the request can sink an approval that would otherwise succeed. Splitting the request, or asking for the narrower package set, is usually the practical fix.
- What does a 500 during approval actually mean?
- Read it as a rejection first and an outage second. Integrators observe server errors in flows where the underlying condition is an authorisation problem, such as a package delegation being refused. Before retrying on a loop, re-check the approver's authority and the requested packages as if the answer had been 403, because in practice that is often what it stood for.
- My token is valid and delegation is active. Why still 403?
- Check which token you presented. Altinn Apps and the classic platform APIs evaluate claims the raw Maskinporten token does not carry, including the authentication level added by the exchange endpoint. A valid system user token that never went through the exchange can fail authorisation with every delegation in perfect order. Exchange first, then call.
- Can Apier take this error class away?
- No, and it does not try. The system user delegation runs between your registered system and the customer inside Altinn, and Apier brokers no part of it; the fullmakt endpoints it used to expose were withdrawn on 2026-10-21 and answer 410 CAPABILITY_WITHDRAWN. What Apier answers is the question before the approval: who in the customer company holds signaturrett or prokura, from Brønnøysund, so the approval link lands with a person whose authority covers what it asks for.