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

# 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 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:

```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 on `YYYY-MM-DD` format, for example `2024-12-31`.

Here is an example request using example values:

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

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

An example response:

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

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

Example response:

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

**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/2023-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": "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
    }
  ],
}
```

**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/2023-08-03` returns 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"`
  - `tryLater` is now `false`.
- `GET /report/v2/ledgers/{ledgerId}/funds/dates/2023-08-04` returns entry B with `"LedgerDate": "2023-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 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:

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).

#### 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}`][fetch-report-by-date-endpoint]
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}`][fetch-report-by-date-endpoint], where `{topic}` is `funds`.
- Does the funds report for that date end with a `payout-scheduled` entry?
  - 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

**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/2023-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":"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](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

> **Full site overview:** For every page in this documentation, read [https://developer.vippsmobilepay.com/llms.txt](https://developer.vippsmobilepay.com/llms.txt).
