Skip to content

Apier.no

Recipes

Human-readable walk-throughs of the canonical agent workflows. Each recipe mirrors exactly one entry in /workflows.json — the machine-readable manifest crawlers and agent frameworks consume. For the full capability listing see /api/v1/capabilities.

Job-shaped recipes

Task-first walk-throughs for the four live combos — job and “for platforms” recipes per combo, plus a composed KYB client-onboarding chain.

Look up Norwegian business deadlines for a year

workflow.lookup-deadlines · Slå opp norske forretningsfrister for et år

Fetch the canonical deadline list (MVA terminer, A-melding, skattemelding, årsregnskap, Foretaksregisteret årsbekreftelse) for a calendar year. Deadlines are pre-adjusted through weekends and Norwegian public holidays, with the original legal deadline surfaced in `adjusted_from` when relevant.

Intent
Find all upcoming Norwegian business deadlines for a given calendar year.
Expected outcome
An ordered list of deadline entries sorted ascending by date, each with obligation_id, legal deadline, adjusted deadline (when pushed for weekends/holidays), and legal reference.
Trigger intent
What Norwegian business deadlines are coming up in YYYY?
Estimated time
~200 ms · zero auth

Steps

  1. 1

    Call GET /api/v1/public/deadlines?year=YYYY and iterate the returned mappings sorted ascending by deadline.

    public.deadlinesJSON envelope with data.year, data.deadlines[] (sorted ascending), and _meta.served_from='static'.

Agent instructions

1. Call GET /api/v1/public/deadlines?year=<YYYY> (no authentication required).
2. Parse the response body at data.deadlines — it is sorted ascending by deadline.
3. For each entry, note obligation_id, deadline (ISO 8601 with Europe/Oslo offset), and adjusted_from (null unless the legal deadline fell on a weekend or holiday).
4. If data.deadlines is empty, the year is valid but outside our coverage window (2020-2100) — surface VALIDATION_FAILED explanation to the user.

Failure modes to plan for

  • invalid_year
  • upstream_timeout

Example request

GET /api/v1/public/deadlines?year=2026

Check a Norwegian company's current Brønnøysund status

workflow.check-company-status · Sjekk en norsk virksomhets gjeldende Brønnøysund-status

Fetch the current Tier 1 + (when delegation exists) Tier 2 view of a Norwegian company by org number. `data.data_tier` tells you whether Tier 2 was served; `_meta.served_from` distinguishes cache from live.

Intent
Retrieve the current Brønnøysund-sourced status and registry metadata for a Norwegian company, including Tier 2 commercial metrics when a delegation is in place.
Expected outcome
Company context object with name, entity_type, NACE codes, status (active/deregistered/dissolved), municipality, signaturrett/prokura — plus Tier 2 fields (employee_count, annual_turnover, total_assets) when the caller has an active delegation.
Trigger intent
Is company with org_number 999999999 still active, and what is its current name?
Estimated time
~500 ms · requires API key (free+)

Steps

  1. 1

    Call GET /api/v1/company/{org}/context with the consumer API key; branch on data.data_tier.

    company.contextJSON envelope with data.company (tier-1 fields), optional data.tier_2 block, data.data_tier, and _meta with served_from + stale markers.

Agent instructions

1. Ensure you hold a consumer API key in the Authorization: Bearer header.
2. Call GET /api/v1/company/<ORG_NUMBER>/context with ORG_NUMBER as a 9-digit Norwegian organisation number.
3. Branch on data.data_tier — 'tier_1' means public-only Brønnøysund fields; 'tier_2' means the delegation is active and commercial metrics are included.
4. Read _meta.served_from ('cache' vs 'live') and _meta.stale (true when a within-limit cached row was served as an upstream-down fallback).
5. On 404 org_not_found: surface the explanation.why text (Norwegian) to the user.
6. On 503 with _meta.stale=true: the data is still usable but older than 24 hours — tell the user freshness is degraded.

Failure modes to plan for

  • org_not_found
  • tier2_data_missing
  • upstream_timeout
  • auth_missing
  • auth_invalid_key

Example request

curl -H 'Authorization: Bearer <API_KEY>' https://www.apier.no/api/v1/company/999999999/context

Look up the official NOK exchange rate for a date

workflow.exchange-rate-lookup · Slå opp offisiell NOK-kurs for en dato

Fetch the Norges Bank NOK rate for a currency and date. Weekend/holiday/pre-publication-today requests fall back to the most recent prior business day; `data.date` is the actual publication date and `data.requested_date` is what was asked for.

Intent
Fetch the official Norges Bank NOK exchange rate for a currency and optional date, with automatic fallback to the most recent prior business day when the requested date is a weekend, holiday, or before today's ~16:00 Oslo publication.
Expected outcome
A rate object: base='NOK', quote=<uppercased ISO>, rate (1 quote = rate NOK), date (actual publication date), requested_date (what you asked for), source='Norges Bank'.
Trigger intent
What was the EUR/NOK rate on 2026-04-10?
Estimated time
~300 ms · zero auth

Steps

  1. 1

    Call GET /api/v1/tools/exchange-rate?currency=<ISO>&date=<YYYY-MM-DD>.

    tools.exchange_rateJSON envelope with data.rate (number), data.date, data.requested_date, data.cached, and _meta.served_from (cache/live).

Agent instructions

1. Call GET /api/v1/tools/exchange-rate?currency=<ISO>&date=<YYYY-MM-DD> (no authentication).
2. CURRENCY must be a 3-letter ISO 4217 code Norges Bank tracks (EUR, USD, GBP, SEK, DKK, etc.).
3. DATE is optional — omit to get today-in-Oslo; future dates are rejected with 400 INVALID_DATE.
4. Compare data.date to data.requested_date. If they differ, the requested date fell on a weekend / holiday / pre-publication window and the rate is from the most recent prior business day.
5. On 404 NO_RATE_AVAILABLE: Norges Bank does not track this currency — try EUR or USD as a fallback.
6. On 502 UPSTREAM_UNAVAILABLE: transient; retry in a few minutes.

Failure modes to plan for

  • invalid_currency
  • invalid_date
  • no_rate_available
  • upstream_unavailable

Example request

GET /api/v1/tools/exchange-rate?currency=EUR&date=2026-04-10

Translate an Altinn 2 code to the Altinn 3 equivalent

workflow.altinn-role-migration · Oversett en Altinn 2-kode til Altinn 3-motparten

Translate an Altinn 2 service or role code to its Altinn 3 equivalent. The 19 June 2026 Altinn 2 deprecation deadline has passed, so this identifies what a legacy code maps to for migration remediation. Gate production migration actions on `verified === true`; unverified entries are convenience references only.

Intent
Discover the Altinn 3 resource or role that replaces a given Altinn 2 service/role code, now that the 19 June 2026 Altinn 2 deprecation cliff has passed.
Expected outcome
A migration entry with altinn2_code, altinn3_code (possibly null if DigDir hasn't published an equivalent yet), migration_notes in Norwegian and English, service_owner, and a `verified` flag indicating whether the mapping has been cross-checked against DigDir's authoritative source.
Trigger intent
What Altinn 3 role corresponds to Altinn 2 code A0208?
Estimated time
~150 ms · zero auth

Steps

  1. 1

    Call GET /api/v1/tools/altinn-migration?altinn2_code=<CODE>; inspect `verified` before trusting the mapping for production automation.

    tools.altinn_migrationJSON envelope with data.entry (MigrationEntry), data.deprecation_deadline, data.days_remaining, data.deadline_passed, and _meta.served_from='static'.

Agent instructions

1. Call GET /api/v1/tools/altinn-migration?altinn2_code=<CODE> (no authentication). CODE is alphanumeric, max 10 characters (e.g. A0208).
2. Read data.entry.verified — if false, the mapping is a best-effort reference and MUST NOT drive production migration without human review.
3. Read data.entry.altinn3_code — null means DigDir has not yet published the Altinn 3 equivalent.
4. The 19 June 2026 Altinn 2 cliff has passed, so data.deadline_passed is true and data.days_remaining is 0 — tell the user the cutoff is already behind them rather than presenting a countdown.
5. On 404 NOT_FOUND: the code is not in our static map — point the user at docs.altinn.studio.

Failure modes to plan for

  • invalid_code
  • not_found
  • unverified_entry

Example request

GET /api/v1/tools/altinn-migration?altinn2_code=A0208

List generic obligations for a Norwegian entity type

workflow.obligation-overview · List generelle forpliktelser for en norsk selskapsform

Fetch the universal obligation set for a given entity type (AS, ENK, ANS, DA, NUF). Template layer only — `tier_2_required: true` entries need commercial data to evaluate per-company, which requires an Altinn delegation.

Intent
List the universal regulatory obligations (tax, reporting, insurance, registration) that apply to a Norwegian entity type, and identify which of those need Tier 2 commercial data to evaluate per-company.
Expected outcome
Obligation template set: each entry names the obligation, category, frequency, required/conditionally/never applicability, a Norwegian condition string, tier_2_required flag, and the Lovdata legal reference.
Trigger intent
What obligations apply to a Norwegian aksjeselskap (AS)?
Estimated time
~150 ms · zero auth

Steps

  1. 1

    Call GET /api/v1/public/obligations?entity_type=<AS|ENK|ANS|DA|NUF>. Use the `tier_2_required` flag to decide whether to plan a delegation flow next.

    public.obligationsJSON envelope with data.entity_type, data.obligations[] (may be empty for ANS/DA/NUF), data.notes[], and _meta.served_from='static'.

Agent instructions

1. Call GET /api/v1/public/obligations?entity_type=<AS|ENK|ANS|DA|NUF> (no authentication).
2. Iterate data.obligations — an array of obligation templates.
3. For each obligation with tier_2_required === true, plan ahead: the caller will need an Altinn delegation (workflow.request-delegation, not yet shipped) before the paid /v1/company/{org}/obligations endpoint can evaluate it per-company.
4. Obligations with required='never' are DELIBERATELY included so agents see they were considered and ruled out (e.g. revisor-plikt for ENK).
5. ANS, DA, and NUF currently return an empty obligations array with a notes entry — templates are not yet published.

Failure modes to plan for

  • invalid_entity_type
  • empty_template_set

Example request

GET /api/v1/public/obligations?entity_type=AS

Reconcile a customer's filing history from the audit trail

workflow.reconcile-filing-history · Avstem en kundes innsendingshistorikk fra sporingsloggen

Pull the consumer's own audit trail (Sporingslogg) for a single org_number across a date range, paging via the keyset cursor (next_before + next_before_id) until exhausted. Useful for accounting firms reconciling activity after the fact, AI-agent operators debugging filing flows, and producing a forensic timeline for dispute defense. Returns ONLY rows YOUR consumer wrote — cross-consumer visibility is structurally impossible.

Intent
Reconstruct the activity timeline a single consumer's API key generated against one Norwegian organisation, optionally narrowed by action type or initiator class, for reconciliation, debugging, or dispute defense.
Expected outcome
Ordered (timestamp DESC, id DESC) page of AuditLogEntry rows the consumer wrote for the org, each carrying correlation_id (joins to provenance_log / evaluation_snapshots / compliance_state_events), initiated_by classification, schema_version, and a scrubbed details JSONB.
Trigger intent
Show me every audit-log row my consumer wrote for org 999999999 in the last 90 days.
Estimated time
~250 ms · requires API key (free+)

Steps

  1. 1

    Call GET /api/v1/company/{org}/audit with the consumer API key (read:audit scope). Iterate the page; if pagination.has_more, repeat with ?before=pagination.next_before&before_id=pagination.next_before_id.

    company.auditJSON envelope with data.data[] (AuditLogEntry array), data.pagination.has_more, data.pagination.next_before (string or absent), data.pagination.next_before_id (string or absent), and _meta with response_timestamp + response_hash.

Agent instructions

1. Ensure your API key has the read:audit scope (operator-issued via /v1/admin/keys).
2. Call GET /api/v1/company/<ORG_NUMBER>/audit?since=<ISO_FROM>&until=<ISO_TO>&limit=200 with the consumer API key.
3. Iterate data.data — each row is an AuditLogEntry. correlation_id joins to provenance_log / evaluation_snapshots / compliance_state_events from the same originating request.
4. If pagination.has_more is true, pass pagination.next_before back as ?before=<value> AND pagination.next_before_id back as ?before_id=<value> to fetch the next page. Both are required together — the cursor is a tuple, not two independent fields.
5. To narrow further: ?action=<closed-enum> or ?initiated_by=<human|agent|cron|system|unknown>. Action enum is the closed list at src/lib/audit/types.ts AUDIT_ACTIONS — anything outside the list is rejected at 400.
6. details.* values matching token / secret / password / bearer / authorization / jwt / api_key / private_key / credential / client_secret / refresh_token are auto-redacted to the literal string [REDACTED] at BOTH write and read time.
7. An empty array (data.data === []) for a valid (consumer, org) pair is returned as 200, not 404 — that prevents existence-leak across consumers.

Failure modes to plan for

  • auth_missing
  • scope_insufficient
  • validation_failed
  • audit_query_failed

Example request

curl -H 'Authorization: Bearer <API_KEY>' 'https://www.apier.no/api/v1/company/999999999/audit?limit=50&initiated_by=agent'

Track historical changes for a customer company

workflow.track-changes · Følg historiske endringer for en kunde-virksomhet

Pull all change events for a specific org_number from the change archive (Brreg ingestion + Altinn / DigDir / Norges Bank multi-source). Cursor-paginated; pass next_cursor back as ?cursor= for the next page.

Intent
Subscribe (via polling) to changes affecting a Norwegian company, audit historical mutations, or reconstruct what Apier saw at a given time across all four upstream sources.
Expected outcome
Page of change events (created/updated/deleted) ordered detected_at DESC, id DESC. Each row carries source_snapshot_id for joining to the response receipt, plus a derived observation_kind (first_observation = the archive's first sighting, value_change = a real observed transition). With entity_id supplied (this workflow's golden path) personal role-holder fields are included; without it, role-holder field rows AND whole-entity company created/deleted rows (their snapshots embed role-holder arrays) are withheld — intentionally, not as missing archive data — and personal_fields_withheld is true.
Trigger intent
Show me everything that changed for org_number 998877665 in the last 30 days.
Estimated time
~200 ms · requires API key (starter+)

Steps

  1. 1

    Call GET /api/v1/changes?source=brreg&entity_id={org_number}&from={iso_date} with the consumer API key (read:changes scope). Iterate the page; if pagination.has_more, repeat with ?cursor=pagination.next_cursor.

    changes.queryJSON envelope with data.data[] (PublicChangeRow array), data.pagination.has_more, data.pagination.next_cursor (string or null), and _meta with response_timestamp + response_hash.

Agent instructions

1. Ensure your API key has the read:changes scope (operator-issued via /v1/admin/keys).
2. Call GET /api/v1/changes with filters: source (brreg|altinn|digdir|norges_bank|nav|skatteetaten), entity_id, change_type (created|updated|deleted), from + to (RFC 3339).
3. Iterate data.data — each row is a created/updated/deleted event with before_value/after_value JSONB blobs, an RFC 6902 diff array, and a derived observation_kind (treat first_observation rows as the archive's first sighting of that field/entity, not a real-world change).
4. If pagination.has_more is true, pass pagination.next_cursor back as ?cursor=<value> to fetch the next page. Do NOT modify the cursor — it's HMAC-signed and tampered cursors return 400 CURSOR_INVALID.
5. correlation_id is intentionally not exposed; use source_snapshot_id to join back to provenance receipts.

Failure modes to plan for

  • auth_missing
  • scope_insufficient
  • cursor_invalid
  • validation_failed

Example request

curl -H 'Authorization: Bearer <API_KEY>' 'https://www.apier.no/api/v1/changes?source=brreg&entity_id=998877665&from=2026-01-01T00:00:00Z'

Validate a filing before submission

workflow.validate-filing · Valider en innlevering før innsending

Run the five preflight checks the dry-run engine performs against a filing payload — company exists, system user authorised, scopes delegated, payload format valid, deadline in future — without submitting anything to Altinn / Skatteetaten / NAV. Use this before any human-approval flow so the agent can present a complete pass/fail picture rather than discovering problems mid-flight.

Intent
Pre-flight a regulatory filing payload to discover every problem in one call, BEFORE the live submission burns an idempotency key or surfaces a partial commit at the upstream.
Expected outcome
DryRunOutcome with all_passed (boolean) and checks[5] (per-check pass/fail with structured error_code on failures). The disclaimer field reminds the agent that a passing dry-run is NOT a guarantee of submission success.
Trigger intent
Will my MVA-melding for 2026-T2 pass before I submit it?
Estimated time
~600 ms · requires API key (starter+)

Steps

  1. 1

    Call POST /api/v1/actions/execute?dry_run=true with { org_number, action_type, period, payload }. The endpoint performs all five preflight checks, writes one evaluation_snapshots row + one audit_log row, and returns 200 with the structured DryRunOutcome.

    actions.execute_dry_runJSON envelope with data.dry_run === true, data.outcome.all_passed (boolean), data.outcome.checks (5 entries, ordered), data.outcome.disclaimer (fixed string), and _meta with response_timestamp + response_hash.

Agent instructions

1. Ensure your API key has the read:actions scope (operator-issued via /v1/admin/keys).
2. Build the request body: { org_number (9 digits), action_type ("mva_melding" | "a_melding"), period (YYYY-Tn|YYYY-A|YYYY-MM for mva; YYYY-MM for a-melding), payload (per-action shape — see OpenAPI components MvaMeldingPayload / AMeldingPayload). Decimals as STRINGS, not numbers.
3. POST /api/v1/actions/execute?dry_run=true with the body. Note ?dry_run=true is what makes this call validate-only — omitting it (or sending any value other than "true") routes to the live-execute path for supported action types instead of a no-op (still mock-routed by default when ALTINN_MODE=mock, not necessarily a real government submission); see /docs/capability-status for the current live/gated status of each action type before relying on that path.
4. Read response data.outcome.all_passed. If true, the dry-run found no blockers; live submission uses the live-execute flow (Idempotency-Key + approval token) and is enabled per-operator once Maskinporten/Altinn grants are active.
5. If false, iterate data.outcome.checks: each row has check (name), passed (bool), error_code (string|null), explanation_summary (string|null). Fix the inputs against the failed checks and retry — the engine never short-circuits, so one call lists every problem.
6. Treat the disclaimer field as a hard contract: a passing dry-run is NOT a guarantee. Government systems may reject otherwise-valid submissions for reasons outside Apier's view.

Failure modes to plan for

  • auth_missing
  • scope_insufficient
  • validation_failed
  • execute_not_available
  • payload_too_large

Example request

Dry-run: POST /api/v1/actions/execute?dry_run=true with headers { Authorization: Bearer <KEY> } and body { org_number, action_type, period, payload }. Live-execute: POST /api/v1/actions/execute with headers { Authorization: Bearer <KEY>, Idempotency-Key: <UUID>, X-Approval-Token: <token> } and body { org_number, action_type: "mva_melding", period, payload }. Both modes require API-key auth via Authorization header (round 5 manifest fix — was previously omitted from the dry-run half).

Onboard a Norwegian client company from a single org number (KYB)

workflow.kyb-client-onboarding · Onboard en norsk kundevirksomhet fra ett organisasjonsnummer (KYB)

Composed five-step KYB (know-your-business) onboarding chain over the live company reads: verify the company exists and is safe to act for, resolve who can legally sign for it, read its filed annual accounts, evaluate its regulatory obligations, and expand its concrete deadline calendar. Every step's response carries `_meta.response_hash` and lands a provenance_log row, so the assembled onboarding answer is provenance-backed end-to-end; the verify and authority steps optionally return an offline-verifiable detached-JWS certificate via `?certificate=true`.

Intent
Onboard a Norwegian client company from a single organisation number: establish that it is real and active, who holds acting capacity, what its filed accounts show, which obligations apply, and when they fall due — one provenance-stamped answer per question.
Expected outcome
A provenance-backed onboarding summary assembled from five responses: a pass/warn/fail verification verdict, a signing-authority classification with the named holders, the filed annual-accounts snapshot with key figures, the evaluated obligation catalogue, and the weekend/holiday-adjusted deadline calendar — each response carrying `_meta.response_hash` (joinable to provenance_log), with optional detached-JWS certificates from the verify and authority steps for offline re-verification.
Trigger intent
Onboard company 999999999 as a client — does it exist, who can sign for it, and what does it owe when?
Estimated time
~2500 ms · requires API key (free+)

Steps

  1. 1

    Call GET /api/v1/company/{org}/verify (optionally ?certificate=true) and gate the rest of the chain on data.verification_status — stop on 'fail'.

    company.verifyJSON envelope with data.verification_status (pass/warn/fail/unknown), data.signals (seven tri-state booleans), and _meta with response_hash; with ?certificate=true the data block is a detached-JWS certificate wrapping the same verdict.

  2. 2

    Call GET /api/v1/company/{org}/authority (optionally ?certificate=true) to resolve who can legally commit the company before any binding onboarding step.

    company.authorityJSON envelope with data.classification (sole/joint/by_role/prokura_only/no_authority/unknown), data.signaturrett_holders, data.prokura_holders, and _meta with response_hash.

  3. 3

    Call GET /api/v1/company/{org}/accounts for the filed annual-accounts snapshot — treat has_filed_annual_accounts null as unknown, never as 'not filed'.

    company.accountsJSON envelope with data.has_filed_annual_accounts (tri-state), data.last_accounts_year, data.key_figures (with explicit currency), and _meta with response_hash.

  4. 4

    Call GET /api/v1/company/{org}/obligations to evaluate every Rulebook obligation for the company — insufficient_data verdicts point at the Tier-2 delegation path.

    company.obligationsJSON envelope with data.obligations[] (one EvaluationResult per rule: applicable/not_applicable/insufficient_data), data.data_tier, and _meta with response_hash.

  5. 5

    Call GET /api/v1/company/{org}/deadlines to expand the applicable obligations into the concrete weekend/holiday-adjusted deadline calendar.

    company.deadlinesJSON envelope with data.deadlines[] (DeadlineEntry rows, due_at ascending, Europe/Oslo offsets), data.data_tier, and _meta with response_hash.

Agent instructions

1. Ensure you hold a consumer API key (Authorization: Bearer) with the read:brreg scope; ORG_NUMBER below is a 9-digit Norwegian organisation number.
2. Call GET /api/v1/company/<ORG_NUMBER>/verify. Branch on data.verification_status: 'fail' means the company is not active — stop onboarding and surface data.summary; 'warn' means active but no signing authority is defined — continue, but flag it; 'pass' continues. Append ?certificate=true when you want the verdict wrapped in an offline-verifiable detached-JWS certificate.
3. Call GET /api/v1/company/<ORG_NUMBER>/authority. Read data.classification (sole / joint / by_role / prokura_only / no_authority / unknown) and data.signaturrett_holders + data.prokura_holders to know who must approve binding steps. ?certificate=true is available here too.
4. Call GET /api/v1/company/<ORG_NUMBER>/accounts. data.has_filed_annual_accounts is tri-state (true / false / null=unknown — never coerce null to false); data.key_figures carries the latest filed year's figures with an explicit currency.
5. Call GET /api/v1/company/<ORG_NUMBER>/obligations. Each EvaluationResult is applicable / not_applicable / insufficient_data — treat insufficient_data as 'needs a Tier-2 delegation', not as a no.
6. Call GET /api/v1/company/<ORG_NUMBER>/deadlines. Entries are already weekend/holiday-adjusted with Europe/Oslo offsets; sort order is due_at ascending.
7. Assemble the onboarding summary from the five data blocks. Keep each response's _meta.response_hash (and the step-2/3 certificates if requested) as the provenance trail for the decision.

Failure modes to plan for

  • org_not_found
  • auth_missing
  • auth_invalid_key
  • scope_insufficient
  • upstream_timeout

Example request

curl -H 'Authorization: Bearer <API_KEY>' https://www.apier.no/api/v1/company/999999999/verify