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.
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
Call GET /api/v1/public/deadlines?year=YYYY and iterate the returned mappings sorted ascending by deadline.
public.deadlines → JSON 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, MVA status) when the caller has an active delegation.
- Trigger intent
- “Is company with org_number 123456789 still active, and what is its current name?”
- Estimated time
- ~500 ms · requires API key (free+)
Steps
- 1
Call GET /api/v1/company/{org}/context with the consumer API key; branch on data.data_tier.
company.context → JSON 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://apier.no/api/v1/company/123456789/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
Call GET /api/v1/tools/exchange-rate?currency=<ISO>&date=<YYYY-MM-DD>.
tools.exchange_rate → JSON 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
Before the 19 June 2026 deprecation deadline, translate an Altinn 2 service or role code to the Altinn 3 equivalent. 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, ahead of the 19 June 2026 deprecation cliff.
- 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
Call GET /api/v1/tools/altinn-migration?altinn2_code=<CODE>; inspect `verified` before trusting the mapping for production automation.
tools.altinn_migration → JSON 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. Surface data.days_remaining to the user so they know how long until the 19 June 2026 deprecation cliff. 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
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.obligations → JSON 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 123456789 in the last 90 days.”
- Estimated time
- ~250 ms · requires API key (free+)
Steps
- 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.audit → JSON 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://apier.no/api/v1/company/123456789/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.
- 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
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.query → JSON 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), 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 and an RFC 6902 diff array. 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://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
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_run → JSON 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 the ?dry_run=true is REQUIRED at v1 — omitting it returns 501 EXECUTE_NOT_AVAILABLE.
4. Read response data.outcome.all_passed. If true, the dry-run found no blockers; proceed to live submission once PR-077 lands.
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).