Fetching report data
Install AI tools
Give your assistant up-to-date guidance for our APIs, with our plugin or without it.
The plugin is still under development. See the AI tools page for details.
- Claude Code
- Cursor
- Codex
- Without installing
Run both commands, in order.
claude plugin marketplace add vippsas/agent-toolkit
claude plugin install vipps-developer@agent-toolkit
Open Settings, then Plugins.
Add vippsas/agent-toolkit as a plugin marketplace, then install the "vipps-developer" plugin.
For the app, add the marketplace "vippsas/agent-toolkit" and then install "vipps-developer". For the CLI, run the following commands.
codex plugin marketplace add vippsas/agent-toolkit
codex plugin add vipps-developer@agent-toolkit
Paste this into any assistant.
Read https://github.com/vippsas/agent-toolkit/blob/main/plugins/vipps-developer/README.md for Vipps MobilePay integration guidance.
For troubleshooting, see the full instructions.
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 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 inYYYY-MM-DDformat, for example2025-12-31.
Here is an example request using example values:
ledgerId:12345topic:fundsledgerDate:2025-12-31
GET:/report/v2/ledgers/12345/funds/dates/2025-12-31
An example response:
{
"hasMore":false,
"tryLater":false,
"items":[
{
"pspReference":"3343121302",
"time":"2025-10-05T00:00:00.000000Z",
"ledgerDate":"2025-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. If hasMore and tryLater are both true, do not wait: keep paging with the returned cursor. See When tryLater is true.
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/2025-12-31 and GET:/report/v2/ledgers/12345/funds/feed endpoints both return one page of items per response. The page size is not a fixed number: 1000 items is the default, and the value resolved for your ledger is returned in the Vipps-Feed-Page-Size response header. Read the page size from that header rather than assuming 1000. If you need larger pages, ask Vipps MobilePay to raise the limit for your ledger. 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 filled the resolved 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 at least 1 second 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": "2025-10-05T00:00:00.000000Z",
"ledgerDate": "2025-10-05",
"entryType": "refund",
"reference": "acme-shop-123-order123abc",
"currency": "NOK",
"amount": -49900,
"balanceBefore": 49900,
"balanceAfter": 0,
"recipientHandle": "NO:123455"
},
{...},
{...}
]
}
When tryLater is trueβ
tryLater tells you that Vipps MobilePay expects more entries for what you asked for. It does not mean the response
is empty, and it does not mean an error occurred.
- On the feed endpoint,
tryLateristrueonly when you have reached the current end of the feed. Wait at least 1 second, then retry with the samecursor. - On the date endpoint,
tryLateristruefor as long as the ledger date is still open, even when the response returns entries. Late entries can still land on an open date, so a response is not complete until the date closes. The 22:30 walkthrough shows this happening entry by entry. Retry an open ledger date after roughly 30 minutes. - An empty response for a date that has already closed returns
tryLater: false. This is how you tell "this ledger date genuinely has no entries" from "the report is not ready yet": an empty response withtryLater: truemeans the date is still open, and an empty response withtryLater: falsemeans the date is closed and there is nothing to report for it.
Do not treat tryLater: false as the only signal that a page is worth storing, and do not treat tryLater: true
as a signal to discard the items you received. A response can return entries and tryLater: true at the same
time. Store the entries, then retry the date later for the rest.
The 30 minute wait is a rule of thumb, not a guarantee that the date has closed by then. Keep polling on your normal
schedule until tryLater turns false.
Response headersβ
Every successful response repeats the paging state in Vipps-Feed-* response headers. Where a header has a
matching field in the JSON body, the two always hold the same value, so use whichever is more convenient.
| Header | JSON body field | Description |
|---|---|---|
Vipps-Feed-Count | - | The number of entries in items. |
Vipps-Feed-Cursor | cursor | The cursor for the next request. Empty when the date endpoint has no next page. |
Vipps-Feed-Has-More | hasMore | Only on the date endpoint. true when the response filled the page size. |
Vipps-Feed-Try-Later | tryLater | true when more entries are expected. |
Vipps-Feed-Page-Size | - | The page size resolved for this ledger. The default is 1000. |
Vipps-Feed-Meta-Version | - | The version of the response body format. |
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/2025-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": "2025-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": "2025-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/2025-08-03returns entry A with"ledgerDate": "2025-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": "2025-08-03" tryLateris nowfalse.
- entry A with a populated
GET /report/v2/ledgers/{ledgerId}/funds/dates/2025-08-04returns entry B with"ledgerDate": "2025-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 a 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 is 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).
Building a report for each payoutβ
A per-payout report shows exactly which entries make up one payout to the merchant's bank account.
Keep fetching your data with Method 1 or
Method 2, and build the per-payout report yourself from the
GET:/report/v2/ledgers/{ledgerId}/{topic}/dates/{ledgerDate}
endpoint.
A per-payout report goes quiet whenever no payout is made. If the balance stays negative for an extended
period of time, no payout-scheduled entry is added, so no per-payout report is produced at all, and your
integration cannot tell that gap apart from a failure to fetch. Keep fetching every ledger date, or read
the feed, rather than fetching only when a payout arrives.
Each payout (each settlement bank transfer) always covers one or more complete ledger dates. A payout never
covers part of a ledger date, and the payout-scheduled entry is always the last entry on a ledger date,
if a payout is made that date. So:
- Start with the earliest ledger date you have not yet included in a report.
- Fetch the data for that date from
GET:/report/v2/ledgers/{ledgerId}/{topic}/dates/{ledgerDate}, where{topic}isfunds. - Wait until
tryLaterisfalse. While a ledger date is still open, thepayout-scheduledentry may not have been added yet, so its absence does not mean a payout was skipped. - Does the funds report for that date end with a
payout-scheduledentry?- If yes, the report is complete. That entry also identifies the payout:
pspReferenceis<ledgerId>-<payoutNumber>, andreferenceis the text attached to the bank transfer. Seepayout-scheduled. - If no, no payout was made at the end of that date (e.g., negative balance, weekly/monthly settlement, or other reasons). Add the next date to the same report and repeat these steps for that date.
- If yes, the report is complete. That entry also identifies the payout:
Using the example in the diagram under Reporting periods, these steps produce two reports.
For payout 2000101, send one request:
GET:/report/v2/ledgers/{ledgerId}/funds/dates/2025-09-01
For payout 2000102, send both of these requests and combine the entries into one report, because no payout was made at the end of 2025-09-02:
GET:/report/v2/ledgers/{ledgerId}/funds/dates/2025-09-02GET:/report/v2/ledgers/{ledgerId}/funds/dates/2025-09-03
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/2025-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":"2025-10-05T10:21:54.141089+0200",
"ledgerDate":"2025-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.