> **Description:** Comprehensive technical documentation for integrating the Report API.

> **AI agent:** Read [https://developer.vippsmobilepay.com/docs/knowledge-base/ai-tools.md](https://developer.vippsmobilepay.com/docs/knowledge-base/ai-tools.md) first. It covers keeping API keys and secrets out of generated code.

> **Full site overview:** For an overview of the entire site, read [https://developer.vippsmobilepay.com/llms.txt](https://developer.vippsmobilepay.com/llms.txt).

# Fetching report data

The Report API exposes three endpoints for retrieving settlement data:

| Endpoint | Description |
|---|---|
| [`GET:/settlement/v1/ledgers`][get-ledgers-endpoint] | 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-report-by-date-endpoint] | Fetch all entries for a specific ledger date. Use this to pull a complete daily report. |
| [`GET:/report/v2/ledgers/{ledgerId}/{topic}/feed`][fetch-report-by-feed-endpoint] | 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](https://developer.vippsmobilepay.com/docs/APIs/report-api/api-guide/settlement-process.md) 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](https://developer.vippsmobilepay.com/docs/APIs/report-api/api-guide/settlement-process.md#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

```mermaid
flowchart LR
    CustomerVN([Customer])
    CustomerLedger([Customer])
    CustomerVN-- Payments -->VMNumber["VM-number #quot;#123456#quot;"]
    CustomerLedger-- Payments -->ecomMSN["eCom MSN #quot;654321#quot;"]
    VMNumber --> LedgerVN["Ledger #quot;123456#quot;"]
    ecomMSN --> LedgerMSN["Ledger #quot;654321#quot;"]
    LedgerVN-- Bulk payout -->BankVN([Merchant's bank account])
    LedgerMSN-- Bulk payout -->BankMSN([Merchant's bank account])
```

Ledger structure: Customer payments made to a VM-number (e.g. "123456") flow into its corresponding ledger ("123456"), which makes a bulk payout to the merchant's bank account. Separately, customer payments made to an eCom MSN (e.g. "654321") flow into its own ledger ("654321"), which also makes a bulk payout to the merchant's bank account.

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`][get-ledgers-endpoint]
once manually to identify the `ledgerId`.

An example response:

```json
{
   "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.

**NOTE**

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:

```json
{
  "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:

```bash
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`][get-ledgers-endpoint] many times as you will continue to see new
ledgers appear for different customers as they
[grant your accounting system access to their data](https://developer.vippsmobilepay.com/docs/APIs/report-api/api-guide/overview.md#give-access-to-an-accounting-partner).

### Retrieving data for a specific date

The
[`GET:/report/v2/ledgers/{ledgerId}/{topic}/dates/{ledgerDate}`][fetch-report-by-date-endpoint]
endpoint lets you get the data for a specific date.

Use the `ledgerId` you got from
[`GET:/settlement/v1/ledgers`][get-ledgers-endpoint]
and specify:

- `topic`:  Either `funds` or `fees`. See [Overview of the settlement process](https://developer.vippsmobilepay.com/docs/APIs/report-api/api-guide/settlement-process.md) for what each topic represents, how net vs gross settlements affect which entries appear, and how to use `pspReference` to correlate entries across both topics.
- `ledgerDate`: The date in `YYYY-MM-DD` format, for example `2025-12-31`.

Here is an example request using example values:

- `ledgerId`: `12345`
- `topic`: `funds`
- `ledgerDate`: `2025-12-31`

[`GET:/report/v2/ledgers/12345/funds/dates/2025-12-31`][fetch-report-by-date-endpoint]

An example response:

```json
{
   "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](#when-trylater-is-true).

**NOTE**

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`][fetch-report-by-feed-endpoint]

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`][fetch-report-by-date-endpoint] and [`GET:/report/v2/ledgers/12345/funds/feed`][fetch-report-by-feed-endpoint] 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`][fetch-report-by-date-endpoint]

Example response:

```json
{
  "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**, `tryLater` is `true` only when you have reached the current end of the feed. Wait at
  least 1 second, then retry with the same `cursor`.
- **On the date endpoint**, `tryLater` is `true` for 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](#method-1-fetching-a-complete-report-for-each-date) 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 with
  `tryLater: true` means the date is still open, and an empty response with `tryLater: false` means the date is
  closed and there is nothing to report for it.

**WARNING**

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.

**TIP**

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](https://developer.vippsmobilepay.com/docs/knowledge-base/api-lifecycle.md).

### 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}`][fetch-report-by-date-endpoint]
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](https://help.vippsmobilepay.com/). It is not (yet) possible for a merchant or partner to configure this.

**NOTE**

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:

```json
{
  "items": [],
  "hasMore": false,
  "tryLater": true
}
```

Eventually, when the ledger has data, you will receive a response with data,
such as:

```json
{
  "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
    }
  ]
}
```

**NOTE**

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](#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-03` returns 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"`
  - `tryLater` is now `false`.
- `GET /report/v2/ledgers/{ledgerId}/funds/dates/2025-08-04` returns entry B with `"ledgerDate": "2025-08-04"` and `"tryLater":true`.

**NOTE**

`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`][fetch-report-by-feed-endpoint]
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:

1. The same cursor over again
2. The `tryLater` field will be set to `true`

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](#method-1-fetching-a-complete-report-for-each-date) or
[Method 2](#method-2-continuous-feed-of-data), and build the per-payout report yourself from the
[`GET:/report/v2/ledgers/{ledgerId}/{topic}/dates/{ledgerDate}`][fetch-report-by-date-endpoint]
endpoint.

**WARNING**

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}`][fetch-report-by-date-endpoint], where `{topic}` is `funds`.
- Wait until `tryLater` is `false`. While a ledger date is still open, the `payout-scheduled` entry 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-scheduled` entry?
  - If yes, the report is complete. That entry also identifies the payout: `pspReference` is
    `<ledgerId>-<payoutNumber>`, and `reference` is the text attached to the bank transfer. See
    [`payout-scheduled`](https://developer.vippsmobilepay.com/docs/APIs/report-api/api-guide/entry-types.md#payout-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.

Using the example in the [diagram under Reporting periods](#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-02`
- `GET:/report/v2/ledgers/{ledgerId}/funds/dates/2025-09-03`

### GDPR data

**INFO**

Personal information is only available for [*Vipps MobilePay number*](https://vippsmobilepay.com/en-NO/in-person) 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](https://developer.vippsmobilepay.com/docs/APIs/userinfo-api/README.md)
and
[Express](https://developer.vippsmobilepay.com/docs/APIs/epayment-api/api-guide/features/express.md).

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=true`
- `GET:/report/v2/ledgers/12345/funds/feed?includeGDPRSensitiveData=true`

Example response when the `?includeGDPRSensitiveData=true` parameter is sent in the request:

```json
"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](https://developer.vippsmobilepay.com/docs/APIs/report-api/api-guide/entry-types.md) -- Descriptions of every entry type that can appear in `funds` and `fees` responses.
- [Overview of the settlement process](https://developer.vippsmobilepay.com/docs/APIs/report-api/api-guide/settlement-process.md) -- How ledgers, payouts, net vs gross settlements, and balances work.

[get-ledgers-endpoint]: https://developer.vippsmobilepay.com/redocusaurus/report-swagger-id.yaml
[fetch-report-by-date-endpoint]: https://developer.vippsmobilepay.com/redocusaurus/report-swagger-id.yaml
[fetch-report-by-feed-endpoint]: https://developer.vippsmobilepay.com/redocusaurus/report-swagger-id.yaml
