Fetching report data
The Report API exposes three endpoints for retrieving settlement data:
| Endpoint | Description |
|---|---|
GET:/settlement/v1/ledgers | List all ledgers you have access to. Every other call requires a ledgerId, so this is always the first step. |
GET:/report/v2/ledgers/{ledgerId}/{topic}/dates/{ledgerDate} | Fetch all entries for a specific ledger date. Use this to pull a complete daily report. |
GET:/report/v2/ledgers/{ledgerId}/{topic}/feed | Stream entries continuously as they arrive. Use this to keep your own database in sync. |
The {topic} path parameter is either funds (captures, refunds, payouts) or fees (per-capture fees and invoices). See Overview of the settlement process for details.
Using the endpointsβ
Retrieving the LedgerIdβ
All Report API payment endpoints are scoped to a ledger, identified by a ledgerId.
A ledger tracks the funds that Vipps MobilePay owes to a merchant and is the basis for settlement payouts.
It is described further in the account diagram in the settlement process overview.
To fetch payments for a specific sales unit, you first need to find the ledgerId for that sales unit.
There is a direct correspondence between a sales unit's ID (MSN) and a ledger. Similarly, each VM-number is directly linked to a specific ledger.
Single sales unit settlement flow
The ledger has its own ledgerId, so the first step in using this API is
to fetch the list of ledgers you have access to. If you are integrating a single
merchant it may be enough to hit
GET:/settlement/v1/ledgers
once manually to identify the ledgerId.
An example response:
{
"items":[
{
"ledgerId":"302321",
"currency":"NOK",
"payoutBankAccount":{
"scheme":"BBAN:NO",
"id":"86011117947"
},
"owner":{
"scheme":"business:NO:ORG",
"id":"987654321"
},
"settlesForRecipientHandles":[
"api:123455",
"api:123456"
],
"salesUnits":[
{
"name":"ACME Fitness Oslo",
"recipientHandle":"api:123455",
"businessIdentifier":{
"scheme":"business:NO:ORG",
"id":"987654321"
}
},
{
"name":"ACME Fitness Bergen",
"recipientHandle":"api:123456",
"businessIdentifier":{
"scheme":"business:NO:ORG",
"id":"987654322"
}
}
]
}
],
"cursor":"eyJhZnRlckxlZGdlcklkIjoie"
}
If you have access to many ledgers, the response will be paged. The cursor is always included β pass it as a query parameter to fetch the next page. When cursor is an empty string, there are no more ledgers to fetch.
The owner of the ledger and the businessIdentifier will be the same.
The businessIdentifier in each salesUnits is included for a possible future support of allowing
one ledger to contain settlement data for multiple sales units from different merchants.
This is not currently supported.
A Vippsnummer or MobilePay-nummer sales unit will use the same settlesForRecipientHandles structure, but will have a different prefix:
{
"settlesForRecipientHandles": [ "NO:123455" ]
}
The prefix for Denmark is DK (e.g., DK:123456), and for Finland it's FI (e.g., FI:123456).
If you only want to look up the ledgerId from an MSN or VM-number, you
may use the settlesForRecipientHandles argument to find the ledgerId for that sales unit:
GET:/settlement/v1/ledgers?settlesForRecipientHandles=DK:123456
If you are integrating an accounting system for many customers, it can be
relevant to poll GET:/settlement/v1/ledgers many times as you will continue to see new
ledgers appear for different customers as they
grant your accounting system access to their data.
Retrieving data for a specific dateβ
The
GET:/report/v2/ledgers/{ledgerId}/{topic}/dates/{ledgerDate}
endpoint lets you get the data for a specific date.
Use the ledgerId you got from
GET:/settlement/v1/ledgers
and specify:
topic: Eitherfundsorfees. See Overview of the settlement process for what each topic represents, how net vs gross settlements affect which entries appear, and how to usepspReferenceto correlate entries across both topics.ledgerDate: The date onYYYY-MM-DDformat, for example2024-12-31.
Here is an example request using example values:
ledgerId:12345topic:fundsledgerDate:2024-12-31
GET:/report/v2/ledgers/12345/funds/dates/2024-12-31
An example response:
{
"hasMore":false,
"tryLater":false,
"items":[
{
"pspReference":"3343121302",
"time":"2020-10-05T00:00:00.000000Z",
"ledgerDate":"2020-10-05",
"entryType":"capture",
"reference":"acme-shop-123-order123abc",
"currency":"NOK",
"amount":49900,
"balanceBefore":49900,
"balanceAfter":49900,
"recipientHandle":"NO:123455"
}
{...},
{...}
]
}
The hasMore field is always included on this endpoint. If hasMore is true, cursor is also included: pass the cursor value as a query parameter to continue fetching entries for the same ledger date. When hasMore is false, cursor is omitted and there is no next page for the current response. If tryLater is also true, retry the same date request later without a cursor.
Monetary amounts are in minor units: for NOK and DKK, 1 kr = 100 ΓΈre (for example, 49900 = 499.00 kr). For EUR, amounts are in cents.
Retrieving all dataβ
This works the same way as above, but the request is without a date specified:
GET:/report/v2/ledgers/12345/funds/feed
The response is similar to the example above; however, the cursor is always included and hasMore is omitted. Always save the returned cursor and use it on the next request, even if no new items are available right now. When you reach the end of the feed, items may be empty and tryLater may be true β wait and retry with the same cursor until new data arrives.
Referenceβ
Paging and cursorsβ
The GET:/report/v2/ledgers/12345/funds/dates/2024-12-31 and GET:/report/v2/ledgers/12345/funds/feed endpoints both return up to 1000 items per response. Use the cursor value from the response to continue fetching data. No other pagination query parameters are needed.
After you have stored or processed the items from the response,
send a new request where the value from the cursor is provided in the query string
like ?cursor=eyJhZnRlckxlZGdlcklkIjoie.
If you use includeGDPRSensitiveData=true, include it again on paginated requests.
For the date endpoint, continue only when hasMore is true and cursor is present.
The value hasMore: true means that the response reached the page size and that you should
continue with the returned cursor. The next response may be empty if that full page was the
last available page. When hasMore is false, cursor is omitted from the JSON body.
For the feed endpoint, always store the returned cursor and use it on the next poll.
The response does not include hasMore. If tryLater is true, no newer data is available
right now; wait before retrying with the same cursor.
For example:
GET:/report/v2/ledgers/{ledgerId}/{topic}/dates/{ledgerDate}?cursor=eyJhZnRlckxlZGdlcklkIjoie
Example response:
{
"hasMore": false,
"tryLater": false,
"items": [
{
"pspReference": "3343121302",
"time": "2020-10-05T00:00:00.000000Z",
"ledgerDate": "2020-10-05",
"entryType": "refund",
"reference": "acme-shop-123-order123abc",
"currency": "NOK",
"amount": -49900,
"balanceBefore": 49900,
"balanceAfter": 0,
"recipientHandle": "NO:123455"
},
{...},
{...}
]
}
Retries of downloads and polling for new dataβ
We recommend that users of this API implement a robust retry mechanism. Sometimes reports can be delayed, or there can be network issues or temporary downtime either at the integrator or at Vipps MobilePay. Rather than, e.g., scheduling a job to run at 08:00 every morning, we instead recommend a pattern where a job is run once every hour of every day. The job should then be programmed to download whatever data is available which has not yet been fetched. This pattern gracefully handles temporary downtime and delays.
If you set up a job every hour, please pick a random minute during the hour when your job runs. If one integrator runs their jobs at :14 after each hour and another at :48, they don't have to compete for resources from this API, and both get a better experience than if they both started their job on :00.
Avoid running your job between :00 and :10, as many integrators default to scheduling at the top of the hour.
Immutability of dataβ
Regardless of which kind of report is fetched, once data is available and has been returned it will be immutable.
The same report fetched at a later point will always contain the same data. If something needs to be corrected, this will be done by adding new correction entries, leaving the old incorrect entries unmodified.
Note however that through upgrades to the API, more types of data (more columns/JSON fields) may become available in historical reports. See: API lifecycle.
Reporting periodsβ
The synchronization process can happen in several ways, as visualized in the following diagram:

Flow diagram: A three-day ledger balance chart. At the top, a continuous green feed endpoint spans all days; below it, individual blue per-date endpoints cover each ledger day. The balance chart shows captures and refunds accumulating within each period, payouts at midnight boundaries that reset the balance, and one period where a negative balance prevents payout generation.
Method 1: Fetching a complete report for each dateβ
The
GET:/report/v2/ledgers/{ledgerId}/{topic}/dates/{ledgerDate}
endpoint offers a complete report per ledger date; indicated by blue in the diagram
above.
Normally, a ledger date lasts from midnight to midnight in the timezone of the merchant, but it can be configured to other cutoffs such as 04:00 to 04:00. To configure the cutoff: Please contact your key account manager, partner manager, or contact our business support. It is not (yet) possible for a merchant or partner to configure this.
An open ledger date is when the ledger has not yet reached the configured cutoff time.
Your code can be written to periodically poll for either closed ledger dates or the current open ledger date.
When polling open ledger dates, you will receive
a response indicating that the report is not yet finalized with tryLater being
true until the ledger has been closed.
Here is an example request:
GET:/report/v2/ledgers/{ledgerId}/funds/dates/2023-08-02
Response:
{
"items": [],
"hasMore": false,
"tryLater": true
}
Eventually when the ledger has data, you will receive a response with data, such as:
{
"cursor": "eyJhZnRlckxlZGdlcklkIjoie",
"hasMore": true,
"tryLater": false,
"items": [
{
"pspReference": "22342342342",
"time": "2023-09-27T14:11:12.640000Z",
"entryType": "capture",
"reference": "acme-shop-123-order123abc",
"currency": "NOK",
"amount": 10000,
"recipientHandle": "NO:12345",
"balanceAfter": 10000,
"balanceBefore": 0
},
{
"pspReference": "22342342342",
"time": "2023-09-27T14:11:12.640000Z",
"entryType": "refund",
"reference": "acme-shop-123-order123abc",
"currency": "NOK",
"amount": -10000,
"recipientHandle": "NO:12345",
"balanceAfter": 0,
"balanceBefore": 10000
}
],
}
In this example, hasMore is true and cursor is present.
This indicates that the provided items is not the full list of data, and that you need
to do another call to continue fetching more data. See the section on paging and cursors.
In the most typical scenario, the balance is zero at the start of a day, funds
are accumulated, and the day ends with a payout-scheduled entry that
pays out the balance and adjusts the balance back down to zero.
There are however other cases:
- If a weekly/monthly settlement has been configured, then the balance will keep accumulating from day to day until the payout is scheduled on the last day of the week/month.
- If the balance is negative (the sum of refunds and fees is higher than the sum of captures), a negative balance is carried over to the next day.
- The agreement with Vipps MobilePay may stipulate that a certain balance is always kept in the ledger to cover possible refunds (so-called "rolling reserve").
By fetching daily reports, you will always get more data every day, even
if payouts are scheduled on a weekly/monthly basis, or if the balance is negative.
The balanceAfter field represents the balance the merchant owns that is sitting
at Vipps MobilePay at any time.
Once a ledger is closed, entries that come after the ledger's final closing time will be available on the next ledger date (today + 1). It is possible to obtain the entries that will be on the next ledger date (tomorrow).
Suppose a ledger is configured to close at 22:30:00 UTC:
- 22:29:00 UTC:
- Fund entry A arrives.
GET /report/v2/ledgers/{ledgerId}/funds/dates/2023-08-03returns entry A with"LedgerDate": "2023-08-03"and"tryLater":true.
- 22:30:00 UTC:
- Ledger closes.
- 22:30:01 UTC:
- Fund entry B arrives and is placed on the next day's ledger.
- After closure, a second request for the current ledger date will return the same entry:
- entry A with a populated
"LedgerDate": "2023-08-03" tryLateris nowfalse.
- entry A with a populated
GET /report/v2/ledgers/{ledgerId}/funds/dates/2023-08-04returns entry B with"LedgerDate": "2023-08-04"and"tryLater":true.
tryLater should always switch to false when a ledger is closed within a few
hours after midnight (and typically a few minutes).
Method 2: Continuous feed of dataβ
The other option is to continuously stream data as it becomes available. You would normally use this to synchronize the data from Vipps MobilePay to your own database, and then it is your own responsibility to do any periodization, if desired. We recommend this way of fetching data in general. Just be aware that it may require some more sophistication in the logic for fetching reports.
The GET:/report/v2/ledgers/{ledgerId}/{topic}/feed
endpoint indicates single "infinite" report, the "feed".
The big difference from the other access methods is that the cursor
will never become empty. Once you have read to the end of the feed
and there will be no more data available, you will receive:
- The same cursor over again
- The
tryLaterfield will be set totrue
It may or may not be that the items list is empty when hitting the
end of the feed in this manner. If you get a response where tryLater is true,
please wait some time (seconds or minutes
or hours depending on your traffic level) and then retry using the same cursor.
If tryLater is false more data is immediately available if you pass in the provided
cursor.
If you poll the endpoint too often, and unless you represent a very high volume merchant, you will end up downloading either 0 or 1 entries each time you call the endpoint, effectively downloading the entries one by one. This is inefficient for both you and Vipps MobilePay; so, unless you need the data with low latency, we recommend just downloading new data relatively seldom (e.g. once per minute or once per hour).
A report for each payout?β
What about a specification for the payout to the bank account of the merchant?
We don't recommend relying on such a report because it will lead to sudden "radio silence" if there is negative balance for an extended period of time. We recommend one of the methods described above.
However, if you need such a specification anyway, it is easy enough to
do yourself using the
GET:/report/v2/ledgers/{ledgerId}/{topic}/dates/{ledgerDate}
endpoint.
Each payout (each settlement bank transfer) will always consist of a whole number
of ledger dates. And, the payout-scheduled entry will always be the last
entry on the ledger date, if a payout is made. So:
- Look up the last date for which you do not have a report
- Fetch data for that date from
GET:/report/v2/ledgers/{ledgerId}/{topic}/dates/{ledgerDate}, where{topic}isfunds. - Does the funds report for that date end with a
payout-scheduledentry?- If yes, stop.
- If no, no payout was made at the end of the day (e.g., negative balance, weekly/monthly settlement, or other reasons) Proceed to the next date and include this in the same report.
Using the example figure above, these steps will produce:
- Report for payout 2000101:
.../dates/2022-09-01/funds
- Report for payout 2000102:
.../dates/2022-09-02/funds.../dates/2022-09-03/funds
GDPR dataβ
Personal information is only available for Vipps MobilePay number merchants.
If this is needed for API payments, merchants can use the built-in functionality to get the user's consent to share personal data - such as phone number, name, email address and address. See Userinfo and Express.
If the parameter includeGDPRSensitiveData is set to true, each payment will contain the
additional fields message, name and maskedPhoneNo.
This is considered user sensitive data and is regulated by GDPR.
Examples
GET:/report/v2/ledgers/12345/funds/dates/2023-12-31?includeGDPRSensitiveData=trueGET:/report/v2/ledgers/12345/funds/feed?includeGDPRSensitiveData=true
Example response when the ?includeGDPRSensitiveData=true parameter is sent in the request:
"items":[
{
"pspReference":"3343121302",
"time":"2020-10-05T10:21:54.141089+0200",
"ledgerDate":"2020-10-05",
"entryType":"refund",
"reference":"acme-shop-123-order123abc",
"currency":"NOK",
"amount":49900,
"balanceBefore":49900,
"balanceAfter":49900,
"recipientHandle":"NO:123455",
"message": "Payment for order 123abc",
"name": "John Doe",
"maskedPhoneNo": "xxxx 5678"
}
]
Related pagesβ
- Entry type reference β Descriptions of every entry type that can appear in
fundsandfeesresponses. - Overview of the settlement process β How ledgers, payouts, net vs gross settlements, and balances work.