Getting started with Maventa ePayslip
Maventa ePayslip is the service Finnish payroll software uses to deliver payslips to employees. Recipients view their payslips in the Maventa ePayslip viewing service, which they reach via a link from their online bank, or receive them as physical letters when electronic delivery is not possible. This page is the entry point for partners building an integration against our API.
REST and SOAP
We run two APIs against the same service:
- REST API — the current API and the one all new integrations use.
- SOAP API — the original API, available only to partners who are already integrated against it.
Over time the REST API will replace the SOAP API. Behaviour, business rules, and the underlying payslip XML are the same on both, so existing functionality is preserved. The rest of this page describes the REST API.
Migrating from the SOAP API to the REST API
A short guide for partners moving from the existing SOAP API to the new REST API. Most of the work is on the wire — the underlying behavior, payslip XML schema (v2.0), and business rules are unchanged.
For full per-endpoint usage details (request bodies, examples, response schemas), use the interactive API browser at https://verkkopalkkademo.maventa.fi/api/scalar/v1 or feed https://verkkopalkkademo.maventa.fi/api/openapi/v1.json into your code generator. The reference is hosted on the demo environment; demo and production expose the same API surface, so it applies to both. This guide only describes what is different between SOAP and REST.
Environments
| Host | |
|---|---|
| Demo / test | https://verkkopalkkademo.maventa.fi |
| Production | https://verkkopalkka.maventa.fi |
Use the demo host while integrating; switch to the production host when ready to go live. All REST API endpoints sit under the /api/ path of these hosts.
Endpoint mapping
| SOAP operation | REST endpoint |
|---|---|
| (none) | POST /api/connect/token (new — auth) |
SubmitPayslips / SubmitDeliveryBatch | POST /api/v1/batches |
GetPayslipProcessingStatusAck | GET /api/v1/batches/status |
DeletePayslip | DELETE /api/v1/payslips |
| (none) | DELETE /api/v1/batches/{batchId}/payslips (new — bulk delete) |
ActivateCustomerPayrollContract | POST /api/v1/payers |
DeactivateCustomerPayrollContract | POST /api/v1/payers/{vatId}/deactivate |
GetContractActiveCustomerIdentifiers | GET /api/v1/payers |
GetContractActiveCustomerVatIdentifiers | GET /api/v1/payers |
GetPayerRegistrationState | GET /api/v1/payers/{vatId} |
| (none) | PATCH /api/v1/payers/{vatId} (new — update name / invoicing customer) |
GetContractedPayslipTransactions | GET /api/v1/payers/transactions |
GetContractedPayslipCustomerCount | removed — use totalPayslipCount from /api/v1/payers/transactions |
GetActiveBankBicCodes | GET /api/v1/banks/bic-codes |
Both SOAP and REST run in parallel — there is no flag day. Migrate one operation at a time and switch traffic at your own pace.
What you have to change in your integration
Authentication: token instead of header credentials
Username + password are no longer in every request’s SOAP header. Instead, call POST /api/connect/token once, cache the returned bearer token, and send it as Authorization: Bearer <token> on every other call.
- Token lifetime: 1 hour. Cache it.
- Same credentials as the SOAP service — no new accounts to provision. Treat them as one credential: a leak, rotation, or password change on either side applies to both APIs at once.
- Token endpoint is rate-limited to 20 requests per 10 minutes, so don’t fetch a new token per call.
- Repeated wrong-password attempts will temporarily lock the account (same behaviour as SOAP). Contact support if you need a password reset.
Batch upload: multipart instead of base64
ZIP file is sent as raw binary in a multipart/form-data field named zipFile instead of base64-encoded inside <ZipFile>…</ZipFile>. About 25 % less bytes on the wire (base64 expansion goes away).
OriginalFileNameSOAP header →fileNameform field.BatchIdis always generated by the server. If you sent your own externally-generated ID before, store the server’s responsebatchIdinstead.Convertflag is gone. Payslip XML must be v2.0. If you still produce 1.1, convert before sending.- Same ZIP rules: ≤ 100 MB, only
.xmland.pdf, optional PDF attachments matched by exact path.
Request and response format: JSON
All non-batch requests use JSON bodies (or query strings) and return JSON responses. The SOAP Acks / Products XML envelopes are replaced with simple JSON arrays and objects — see Scalar UI for the exact shapes.
- Property names use
camelCase. - Date-range filters use half-open intervals
[start, end). To query all of March 2026:startDate=2026-03-01&endDate=2026-04-01. - Status query date range max 24 hours; transactions max 31 days.
Errors: HTTP status codes + ProblemDetails JSON
FaultException<BatchDeliveryFault> is replaced by HTTP status codes plus an RFC 9457 application/problem+json body. The human-readable error reasons in the detail field are the same strings you handle today (BankNotIdentified, NoValidContract, Duplicate, Print delivery requires CountryCode, etc.).
| Status | When |
|---|---|
| 400 | Validation error |
| 401 | Missing / invalid / expired token |
| 404 | Resource not found, OR cross-tenant access (never an info leak), OR targeting a print-only payslip on a delete endpoint |
| 409 | Conflict (payer already active; delete matches multiple rows — provide messageId) |
| 429 | Rate limited (1000 req/min global per client) |
| 500 | Server error — include the traceId field from the error response body, the timestamp, and the endpoint when contacting support |
Dates: UTC with Z
Always serialize as UTC with the Z suffix: 2026-05-04T10:32:11Z. Local-time strings are not accepted.
SOAP fields and operations that no longer exist
A small list so you don’t go hunting for them:
| Gone | Replacement |
|---|---|
GetContractedPayslipCustomerCount operation | totalPayslipCount field in GET /api/v1/payers/transactions |
Convert flag and payslip XML 1.1 support | Convert to 2.0 before sending |
Client-supplied BatchId on submit | Server-generated, returned in response |
customerId Guid on deactivation | vatId in the URL path |
personId, dateOfPayment, receivedDate on DeletePayslip | messageId (or batchId) is now the canonical selector. Capture messageId on submit if you don’t store it today |
externalIdentifier on payer activation | vatId is the stable key |
Migration checklist
- Implement a token cache that fetches on startup and refreshes before the 1-hour expiry.
- Switch your batch upload from base64-in-XML to multipart form upload. Drop your client-side
BatchId— store the server’s response. - Make sure your code records
MessageIdper submitted payslip — it’s the only stable selector forDELETE /api/v1/payslips. - Switch SOAP XML parsing to JSON for status, payer, transaction, and BIC code responses. The error strings in
description/detailare unchanged — map them through. - Switch
Activate/Deactivatepayload construction to the JSON / URL-path shapes documented in Scalar. - Handle HTTP status codes plus ProblemDetails JSON for error responses.
- Ensure all outbound dates are UTC with
Z. - Test in our demo environment
Where to find usage details
- Full REST documentation (concepts, packaging rules, print delivery, payer activation, billing): https://maventaverkkopalkka.fi/verkkopalkan-kayttoonotto/
- Interactive UI (try every endpoint, generate code samples in your language):
- Demo:
https://verkkopalkkademo.maventa.fi/api/scalar/v1 - Production: not available — use the demo environment for testing
- Demo:
- OpenAPI 3 schema (feed into NSwag, openapi-generator, etc.):
- Demo:
https://verkkopalkkademo.maventa.fi/api/openapi/v1.json - Production: not available — use the demo schema
- Demo:
- Support: same channel as for the SOAP API. When reporting an issue, include the
traceIdfield from the error response body, the timestamp, and the endpoint you called.
Environments
| Host | |
|---|---|
| Demo / test | https://verkkopalkkademo.maventa.fi |
| Production | https://verkkopalkka.maventa.fi |
All REST API endpoints sit under the /api/ path of these hosts — for example, https://verkkopalkkademo.maventa.fi/api/connect/token. The root of each host serves the employee viewing service, not the API.
Build and test against demo. Move to production once your integration is verified.
API reference
The full API reference, including request and response shapes, examples in curl and several languages, and a button to call every endpoint live, is generated from the API itself and is always up to date. It is hosted on the demo environment; demo and production expose the same API surface, so the reference applies to both.
- Interactive API browser (Scalar):
https://verkkopalkkademo.maventa.fi/api/scalar/v1 - OpenAPI 3 schema:
https://verkkopalkkademo.maventa.fi/api/openapi/v1.json— feed into your code generator (NSwag, openapi-generator, etc.) to produce a typed client in your language.
The rest of this page only covers the things that the API reference does not — concepts, packaging rules, and the print options that live inside the payslip XML.
Quick start in six steps
- Request credentials by contacting our sales team via the Contact page. You receive a
client_idandclient_secretfor the demo environment, and separate ones for production. - Get an access token from
POST /api/connect/tokenusing the OAuth 2.0client_credentialsgrant. Tokens are valid for one hour — cache and reuse them. - Activate the payer (the company on whose behalf you will send payslips) with
POST /api/v1/payers. This must be done once per company before any payslips for that company can be accepted. - Submit a payslip batch to
POST /api/v1/batchesas amultipart/form-dataupload containing one ZIP file with the payslip XML(s) and optional PDF attachments. - Poll
GET /api/v1/batches/statusfor processing results. Each payslip is acknowledged withOkor with a clear error message. - Move to production — change the base URL, swap to your production credentials, and you’re live.
Other operations are available for day-to-day work — deleting payslip(s) when the data needs correcting, updating a payer’s name or invoicing details, deactivating a payer, fetching billing transactions for invoicing your own customers, and listing supported banks. All are listed in the API reference linked above.
The token endpoint is rate-limited to 20 requests per 10 minutes; all other endpoints share a 1000-requests-per-minute limit per client.
Payslip batches
A batch is a single ZIP file containing one or more payslip XML documents (PayslipXML schema 2.0) and any PDF attachments referenced from them. The maximum size is 100 MB. The server returns a batchId in the submit response — store it together with each payslip’s messageId, since those are the keys used by later status and delete calls.
Each payslip declares one or more delivery channels:
| Code | Channel | What happens |
|---|---|---|
01 | ePayslip | Delivered electronically; the employee opens the payslip in the Maventa ePayslip viewing service via a link from their online bank. |
02 | Printed and mailed as a physical letter. | |
01 + 02 | Both | Both of the above for the same payslip. |
For ePayslip delivery, the recipient’s bank is identified from the BankCode element or, as a fallback, from the IBAN in BankAccount. The live list of supported banks and their BIC codes is available at GET /api/v1/banks/bic-codes.
Print delivery — only needed if you send physical letters
Print delivery
Print delivery is configured in the payslip XML, so it works the same whether you submit batches via the REST API or the SOAP API.
When a payslip is delivered as a physical letter, the print-specific settings live inside the payslip XML — they are part of the schema rather than separate REST fields.
Recipient address
The recipient block is built from <Delivery><Recipient> and from the print delivery channel’s <DeliveryInfo>. The table below shows where each XML element goes: onto the cover page (the first printed page, visible through the envelope window), into a form field sent to the printing service, or both.
| XML element | Required | Cover page | Printing service field | Effect |
|---|---|---|---|---|
RecipientName.ForeName + SurName | Schema | Addressee window — name line | recipient[name] | Name shown on the letter and used as the addressee. |
AddressLine (one or more) | Schema | All lines printed in order | recipient[address] (all lines, joined with a space) | All lines are forwarded to the printing service in the order given. |
PostalCode | Schema | Below address | recipient[post_code] | Postal routing. |
PostOffice | Schema | Below postal code | recipient[post_office] | City / post office. |
CountryCode (ISO 3166-1 alpha-2) | Print delivery | — (not printed) | recipient[country] | Tells the printing service whether the letter is domestic or foreign — the routing fee follows this. |
Country (full name) | Foreign print | Last address line for foreign mail; suppressed for Finland / Suomi | — | Cover-page only. Write it in the language you want printed on the envelope. |
<DeliveryInfo><RecipientTelephone> | Optional | — (not printed) | recipient | Used only for digital-post lookups (helps match the recipient to their OmaPosti account). International format, e.g. +358401234567. |
<DeliveryInfo><RecipientEmailAddress> | Optional | — (not printed) | recipient[email] | Same digital-post matching as phone. |
Sender address
The sender block is built from <Delivery><Sender> and <HeaderData><PartyIdentifications>.
| XML element | Required | Cover page | Printing service field | Effect |
|---|---|---|---|---|
Sender.Name (first non-empty) | Schema | Return-address — name line | sender[name] | Name shown as the letter’s sender. |
Sender.AddressLine (one or more) | Schema | All lines printed in order | — | Cover-page only. |
Sender.PostalCode | Schema | Below address | — | Cover-page only. |
Sender.PostOffice | Schema | Below postal code | — | Cover-page only. |
Sender.Country | Optional | Suppressed for Finland / Suomi; printed otherwise | — | Cover-page only. Posti does not require a sender country on outbound mail, so Finnish payers can leave this empty. |
<HeaderData><PartyIdentifications> with Authority="OVT" or Authority="EIA" | Optional | — (not printed) | sender[sender_id] | Needed for OmaPosti digital routing. The first OVT/EIA entry is used; other authorities (Y-tunnus, VAT, EDI, GLN, …) are deliberately ignored because they would be rejected by the digital route. |
The values in the Required column mean:
- Schema — required by the PayslipXML 2.0 schema. Missing-element payslips fail XSD validation and the whole batch is rejected.
- Print delivery — required only when the payslip uses print delivery (
DeliveryMethodCode = 02or both). Missing-element payslips are rejected during batch processing with the message ”Print delivery requires CountryCode (ISO 3166-1 alpha-2)”. - Foreign print — required only for foreign print delivery (CountryCode ≠
FI). Rejection message: ”Foreign print delivery requires both CountryCode and Country (full name for envelope)”. - Optional — not enforced; supplying it just enables additional functionality (e.g. OmaPosti routing, digital-post matching).
Cover-page address layout
Both blocks are rendered in Arial 10 pt into fixed boxes whose dimensions and positions match the printing service’s cover page layout. The printing service publishes the full specification — see the layout design instructions and the cover page template.
| Box | Lines, in order |
|---|---|
| Sender | Name → each AddressLine on its own line → PostalCode + PostOffice joined with a space → Country (only when not Finnish) |
| Recipient | First name + surname → each AddressLine on its own line → PostalCode + PostOffice joined with a space → Country (only when not Finnish) |
Capacity (in addition to the fixed name + postal/city + country lines):
- Sender box fits up to 4
AddressLinelines (7 lines total). - Recipient box fits up to 5
AddressLinelines (8 lines total).
Wrapping rules:
- A line that is too wide is wrapped at word boundaries onto the next line, consuming one of the available lines above.
- A single word wider than the box itself is broken across characters.
- If the resulting text does not fit vertically into the box, the whole payslip is rejected during batch processing — the address would otherwise be silently clipped on the printed letter. The rejection message names which box (sender or recipient) and asks you to shorten the name, address lines, postal code, city, or country.
If a partner’s data hits the limit consistently, the practical fix is to reduce the number of AddressLine entries — for example, merge ”Long Street Name 12” and ”A 5” into a single AddressLine. Empty <AddressLine></AddressLine> elements are dropped automatically and do not consume a line, so they are safe to leave in.
Letter options (PrintingInfo)
<DeliveryChannel>
<DeliveryMethodCode>02</DeliveryMethodCode>
<DeliveryInfo>
<PrintingInfo>
<LetterClass>economy</LetterClass> <!-- economy (default) | priority -->
<Form>color_print,duplex</Form> <!-- comma-separated, optional -->
</PrintingInfo>
</DeliveryInfo>
</DeliveryChannel>| Setting | Values | Default |
|---|---|---|
LetterClass | economy, priority | economy |
Form | color_print, duplex (any combination, or omit) | Black & white, single-sided |
Attachments
PDF attachments referenced from the payslip XML are appended after the payslip pages. They are printed as supplied, so leave at least a 17 mm top margin and 3 mm side and bottom margins in any PDF you reference — otherwise content near the edge can be clipped.
OmaPosti routing
If the recipient has registered for OmaPosti and chosen to receive mail digitally, the letter is delivered to their OmaPosti inbox instead of being physically mailed. Recipients activate the service themselves at posti.fi; we cannot enroll them. The digital lookup uses the sender OVT (see the sender table above) plus, when supplied, the recipient phone and email; without an OVT the letter is still printed and mailed normally, only the digital routing step is skipped.
Activating payers
A payer (the company on whose behalf you are sending payslips) must be activated before payslips for that payer can be submitted: POST /api/v1/payers. The payer record holds the company name, business ID, and an optional separate invoicing company — this is the information used to attribute and invoice the payslips sent on that company’s behalf. Once activated, the payer remains active until you deactivate it. The same endpoint family covers listing, updating, and deactivating payers — see Scalar for shapes.
When a payslip arrives, the payer is identified by the first <PartyIdentification> entry in <HeaderData><PartyIdentifications>. Its value must match the business ID of an activated payer, otherwise the payslip is rejected. Put the business ID first. If you also include an OVT identification (used to enable OmaPosti digital routing for print delivery — see the sender table above), add it as a separate <PartyIdentification> after the business ID; placed before it, the OVT would be treated as the payer ID and no match would be found.
Reporting and billing
Billing depends on the delivery method. Electronic delivery is counted per payslip; print delivery is billed per letter on top of that, separately from the payslip transaction count.
| Delivery method | Counted as ePayslip transaction | Billed for print |
|---|---|---|
ePayslip only (01) | yes | — |
Print only (02) | — | yes |
Both (01 + 02) | yes | yes |
The transactions endpoint covers only the ePayslip side: GET /api/v1/payers/transactions returns one row per delivered electronic payslip plus a total count for the period (max 31 days per query). Useful if you want to invoice your own customers from it. Print-only payslips do not appear in this response.
Print is billed per produced letter on the factors that affect production cost:
- Number of A4 pages (first page plus additional pages)
- Black & white vs colour
- Economy vs priority class
- Domestic (Finland) vs foreign destination
Support
Technical questions, credential requests, and billing enquiries: see the Contact page for the right team and channel.
When reporting an API issue, please include the traceId field from the error response body, the timestamp, and the endpoint you called.