How do I build compliant AI agents for Norwegian businesses?
Split the work so the model never carries the legal weight. Three pieces do that: deterministic tools that evaluate versioned rules and return a verdict, a scoped credential plus a customer delegation that bound what the agent can reach at all, and an audit trail that records what happened with an identifier you can join on later. The model chooses which tool to call and how to explain the result. It does not decide what Norwegian regulation requires, because a fluent guess and a correct answer are indistinguishable in its output.
Why shouldn't the model interpret Norwegian law directly?
Because you cannot tell from the output whether it knew. Ask a language model when a Norwegian limited company's VAT return is due and you will get a specific date in a confident sentence. That sentence has the same shape whether the model has the rule right, has it approximately right, or has reproduced a rule that changed two years ago. There is no confidence signal in the text that correlates with correctness, so a downstream check on the wording buys you nothing.
A rule evaluation has properties a generated sentence does not. It is deterministic, so the same company and the same rule version always produce the same verdict. It is versioned, so the answer carries a rulebook_version that says which rule set produced it. And it is inspectable: when someone disputes the outcome, you can point at the rule that applied rather than at a paragraph the model wrote. Those three properties are what make an answer defensible rather than merely plausible.
The practical shape is a tool boundary. The model reads the user's question, picks the tool, and turns the structured verdict into an explanation. The tool does the deciding. If your prompt contains a summary of Norwegian filing rules, you have put the regulation back inside the model and reintroduced exactly the failure this structure exists to prevent.
How do I scope what an agent is allowed to do?
Two independent limits, and it is worth understanding that they are independent. The first is the API key's scopes, which say which data sources this credential may reach: registry reads, Altinn reads, policy and exchange-rate reads, the change archive, the dry-run validation surface, the audit trail, webhook subscriptions and key administration. Each names a data source rather than a capability tier, so a key's scope list tells you what the agent touched, not just how much it was trusted.
Read that list as the set in use rather than as a closed vocabulary. What is actually enforced is the domain:action format and the reserved prefixes, so a scope name outside the list is not rejected for being unrecognised. The practical consequence is that granting a scope is not the same as a scope meaning something: check what an endpoint requires rather than assuming a plausible-looking name is wired to anything.
The second limit is the delegation, which says which companies this agent may act for. A key with every assignable scope still answers nothing company-private about a company that has not granted your system access. That separation is the useful one when you are reasoning about blast radius: a leaked key is bounded by the delegations that exist, and a revoked delegation narrows what the key can reach without anyone rotating it.
Wildcards are narrower than they look, deliberately. read:* grants any read scope, but the domain wildcard *:*is rejected by both the database constraint and the middleware, so there is no scope string that means “ everything”. And the write, act and delegate prefixes are reserved: they are not assignable to any key, so a key holding one grants nothing. Scope your agent to the narrowest set that works, then let the delegation do the rest.
What should the audit trail capture?
Enough to reconstruct a decision without re-running it. Every call carries a correlation id, echoed on the response header and written to the audit row behind the call, so one identifier stitches together the request, the evaluation and the record. Store it. It is the difference between being able to answer “why did your product tell my client that” and being able to say only that your product did.
The rows Apier writes record who acted, on which organisation, doing what, under which rule version, and whether the action initiated with a human or a machine. That last field matters more as agents do more: an audit trail that cannot distinguish an operator from an autonomous caller answers the wrong question. What those rows do not capture is your side of the boundary, which is the agent's reasoning, the tool it chose, and what it told the user.
So log both halves and join them on the correlation id. For inspecting the Apier half, the audit endpoint returns your own rows for a given organisation, and the audit inspection guide walks through reconstructing a single action from a receipt through to the entries behind it.
Make the first call
The sandbox call returns the response shape an agent tool actually consumes, so it is a reasonable thing to build a tool schema against. The second reads back what happened afterwards.
# Zero-auth sandbox: the shape an agent tool actually consumes.
curl -s https://www.apier.no/api/v1/sandbox/public/company/999999999/summary// What the agent did, after the fact. Scoped to your own rows.
const res = await fetch(
"https://www.apier.no/api/v1/company/999999999/audit",
{ headers: { Authorization: `Bearer ${process.env.APIER_API_KEY}` } },
);
const { data } = await res.json();
for (const entry of data.entries) {
// correlation_id joins this row to the response the agent saw.
console.log(entry.timestamp, entry.action, entry.correlation_id);
}Frequently asked questions
- Should I let the model reason about Norwegian regulation directly?
- No. A language model asked what a Norwegian company must file will produce a fluent answer whether or not it knows, and you cannot tell the two apart from the output. Put the regulation behind a tool that evaluates versioned rules and returns a verdict with the rule version attached. The model then decides which question to ask, which is the thing it is genuinely good at.
- What scopes can I give an agent's API key?
- The ones you will actually use are read:brreg, read:altinn, read:digdir, read:norgesbank, read:changes, read:actions, read:audit, subscribe:webhooks and admin:keys. Treat that as the working list rather than a closed set: what the database and middleware enforce is the domain:action format and the reserved prefixes, not an allowlist of names. Wildcards work on the action segment only, so read:* grants any read scope while the domain wildcard is rejected outright. The write, act and delegate prefixes are reserved and cannot be granted to any key at all.
- How do I make an agent's behaviour reproducible?
- Store the rulebook_version and the correlation id from every response the agent acted on. The same inputs against the same rule version always produce the same output, so those two fields turn a past decision into something you can re-derive rather than re-argue. Without them you have a log of what your agent said, which is not the same as a record of why.
- Can an agent hold a key that lets it act autonomously?
- It can read and validate autonomously. It cannot release a binding action, because the scope that would let it is reserved and not assignable to any key. This is a structural limit rather than a configuration choice, which means the security review question is not whether you scoped the key correctly but whether the scope exists at all.
- What should I log on my side?
- At minimum the correlation id, the tool called, the inputs, the verdict, and the rulebook version. Apier writes its own audit row per call, but that records what the API did, not what your agent concluded from it or what it then told a user. The join between the two is the correlation id, so carry it into your own records rather than generating a parallel identifier.