Menu

Skatteverket

Skatteverket: filed VAT declarations and their decisions, AGI pre-validation, and syncing the skattekonto now.

Endpoints


GET /api/v1/companies/:companyId/skatteverket/vat-declarations

skatteverket.vat_declarations.get · scope compliance:read

Read a filed momsdeklaration (submitted and/or decided) from Skatteverket.

Fetches the momsdeklaration for one period as Skatteverket has it on file: submitted is the declaration as filed (SKV /inlamnat), decided is Skatteverket's beslut (SKV /beslutat). Either section is null when nothing is on file for the period (or when excluded via ?state=). Query params: period_type (monthly|quarterly|yearly), year, period (1-12 monthly, 1-4 quarterly, 1 yearly), optional state (submitted|decided|both, default both). Requires the company to have an active Skatteverket connection (any member's BankID connection, or a verified ombud grant). Live read against Skatteverket, not a cached copy.

Use when: You want to verify what was actually filed for a VAT period, compare a period against last year's filed declaration, or check whether Skatteverket has decided a period.

Don't use for: Computing the declaration from the books (use the VAT report), or filing: submission is a separate BankID-signed flow.

Pitfalls

  • This is a live Skatteverket read: it fails with SKATTEVERKET_NOT_CONNECTED (401) when the company has neither a member's BankID connection (made under Installningar) nor a verified ombud grant, and the response reflects SKV's state, not the books. Personal BankID sessions expire after ~1 hour by design, so an expired connection is normal: ask the user to reconnect; only a person can, so do not retry until they confirm.
  • submitted=null and decided=null with HTTP 200 means "nothing on file for the period": it is not an error.
  • A submitted declaration can lack a beslut for days: poll decided separately rather than assuming both appear together.
  • redovisningsperiod is SKV's YYYYMM format (the period's LAST month): quarterly period 1 is 03, not 01.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no

Query parameters

NameTypeRequired
period_type"monthly" | "quarterly" | "yearly"yes
yearnumberyes
periodnumberyes
state"submitted" | "decided" | "both"no

Response fields

NameType
redovisarestring
redovisningsperiodstring
submittedunknown (optional)
decidedunknown (optional)

Example request

{
  "period_type": "quarterly",
  "year": 2026,
  "period": 1
}

Example response

{
  "data": {
    "redovisare": "165560000167",
    "redovisningsperiod": "202603",
    "submitted": {
      "mervardesskattTillfalle": "2026-04-10"
    },
    "decided": null
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/skattekonto/sync

skattekonto.sync · scope transactions:write

Fetch the skattekonto from Skatteverket now instead of waiting for the hourly sync.

Reads the company's skattekonto saldo and transactions (booked and upcoming) from Skatteverket and stores them, then refreshes the booking proposals and the reconciliation snapshot. Books nothing: booking skattekonto rows is a separate step. Read-only on Skatteverket's side. Runs on the company's connection (any member's BankID connection, or a verified läsombud grant). Idempotent. Dry-runnable: the dry run checks the connection locally and never calls Skatteverket.

Use when: A payment to or from the skattekonto was just made and the reconciliation or the booking proposals should see it now.

Don't use for: Booking skattekonto rows, or importing a skattekonto file (POST /imports/skattekonto-file).

Pitfalls

  • Needs a live Skatteverket connection: 401 SKATTEVERKET_NOT_CONNECTED when the company has none or it expired (personal BankID sessions last about 1 hour by design). Only a person can reconnect; do not retry until they confirm.
  • The paid Skatteverket capability is required: 403 SKATTEVERKET_CAPABILITY_BLOCKED otherwise.
  • Skatteverket only returns roughly the last 555 days; older history comes from a skattekonto file import.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Response fields

NameTypeDescription
bookednumberNew or status-promoted booked rows.
upcomingnumberNew or updated upcoming rows.
skippednumberRows dropped because Skatteverket omitted a required field.
saldo_skatteverketnumberBalance at Skatteverket after the sync (negative = debt).
saldo_kronofogdennumber
synced_atstring

Example response

{
  "data": {
    "booked": 3,
    "upcoming": 1,
    "skipped": 0,
    "saldo_skatteverket": -1240,
    "saldo_kronofogden": 0,
    "synced_at": "2026-09-26T08:00:00.000Z"
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/skatteverket/agi/validate-huvuduppgift

skatteverket.agi-validate-huvuduppgift · scope compliance:read

Pre-validate an AGI huvuduppgift at Skatteverket without filing anything.

Sends one arbetsgivardeklaration huvuduppgift (AGI API v1.7 section 7: agRegistreradId, redovisningsPeriod, the totals) to Skatteverket's /kontrollera and answers its kontrollsvar: an overall status and each finding. Skatteverket saves nothing; the only local write is the regulator audit row. Uses the calling user's own Skatteverket connection. Live call, not cached.

Use when: Checking a hand-built or externally generated huvuduppgift before filing it, e.g. from a payroll system outside Accounted.

Don't use for: Filing (POST /salary-runs/{id}/generate-agi, then the BankID-signed submission) or checking a salary run booked in Accounted (the submission flow validates it).

Pitfalls

  • Needs a live Skatteverket connection: 401 SKATTEVERKET_NOT_CONNECTED when the company has none or it expired (personal BankID sessions last about 1 hour by design). Only a person can reconnect; do not retry until they confirm.
  • redovisningsPeriod is YYYYMM and no earlier than 201807; amounts are whole kronor.
  • A payload that breaks the v1.7 schema answers 400 VALIDATION_ERROR before anything reaches Skatteverket.
  • status OK or INFO means Skatteverket would accept the figures; it never checks them against the books.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no

Request body

NameTypeRequired
agRegistreradIdstringyes
redovisningsPeriodstringyes
summaSkatteavdrnumberno
summaArbAvgSlfnumberno
totalSjuklonekostnadnumberno

Response fields

NameTypeDescription
uppgift"huvuduppgift" | "individuppgift"
status"OK" | "INFO" | "ARENDE" | "STOPP" | "AVVISANDE"Skatteverket's verdict: OK, INFO (notes only), ARENDE (would open a case), STOPP or AVVISANDE (would be refused).
felobject[]Each finding with its own severity; empty when status is OK.

Example request

{
  "agRegistreradId": "165560000167",
  "redovisningsPeriod": "202609",
  "summaSkatteavdr": 0
}

Example response

{
  "data": {
    "uppgift": "huvuduppgift",
    "status": "INFO",
    "fel": [
      {
        "status": "INFO",
        "felmeddelande": "Summa skatteavdrag är 0."
      }
    ]
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/skatteverket/agi/validate-individuppgift

skatteverket.agi-validate-individuppgift · scope compliance:read

Pre-validate one AGI individuppgift at Skatteverket without filing anything.

Sends one individuppgift (AGI API v1.7 section 8: the payee, specifikationsnummer, cash pay, benefits, preliminary tax and flags) to Skatteverket's /kontrollera and answers its kontrollsvar. Skatteverket saves nothing; the only local write is the regulator audit row. Uses the calling user's own Skatteverket connection. Live call, not cached.

Use when: Checking a hand-built or externally generated individuppgift before filing it.

Don't use for: Filing, or salary runs booked in Accounted (the AGI submission flow builds and validates their individuppgifter).

Pitfalls

  • Needs a live Skatteverket connection: 401 SKATTEVERKET_NOT_CONNECTED when the company has none or it expired (personal BankID sessions last about 1 hour by design). Only a person can reconnect; do not retry until they confirm.
  • betalningsmottagarId is the payee's personnummer (12 digits): it is sent to Skatteverket and not stored by Accounted beyond the audit row's metadata.
  • forstaAnstalld and vaxaStod are mutually exclusive (400 VALIDATION_ERROR).
  • A payload that breaks the v1.7 schema answers 400 VALIDATION_ERROR before anything reaches Skatteverket.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no

Request body

NameTypeRequired
agRegistreradIdstringyes
redovisningsPeriodstringyes
betalningsmottagarIdstringyes
specifikationsnummernumberyes
kontantErsattningUlagAGnumberno
avdrPrelSkattnumberno
skatteplBilformanUlagAGnumberno
drivmVidBilformanUlagAGnumberno
kostformanUlagAGnumberno
skatteplOvrigaFormanerUlagAGnumberno
bostadsformanSmahusUlagAGbooleanno
bostadsformanEjSmahusUlagAGbooleanno
kontantErsattningEjUlagSAnumberno
skatteplBilformanEjUlagSAnumberno
drivmVidBilformanEjUlagSAnumberno
kostformanEjUlagSAnumberno
skatteplOvrigaFormanerEjUlagSAnumberno
bostadsformanSmahusEjUlagSAbooleanno
bostadsformanEjSmahusEjUlagSAbooleanno
formanHarJusteratsbooleanno
forstaAnstalldbooleanno
vaxaStodbooleanno
borttagbooleanno

Response fields

NameTypeDescription
uppgift"huvuduppgift" | "individuppgift"
status"OK" | "INFO" | "ARENDE" | "STOPP" | "AVVISANDE"Skatteverket's verdict: OK, INFO (notes only), ARENDE (would open a case), STOPP or AVVISANDE (would be refused).
felobject[]Each finding with its own severity; empty when status is OK.

Example request

{
  "agRegistreradId": "165560000167",
  "redovisningsPeriod": "202609",
  "betalningsmottagarId": "19800101XXXX",
  "specifikationsnummer": 1,
  "kontantErsattningUlagAG": 35000,
  "avdrPrelSkatt": 8200
}

Example response

{
  "data": {
    "uppgift": "individuppgift",
    "status": "OK",
    "fel": []
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}