Apier

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. 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, 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. 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://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. 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

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. 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. 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. 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 123456789 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://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. 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), 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. 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 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).