# Get account and transaction information

This tutorial presents how to pull the data of a user's account from the `/accounts` endpoint.

Similar steps apply to all the [**Accounts**](/docs/api/open-banking) and [**Transactions**](/docs/api/open-banking) endpoints; therefore, after you complete this tutorial, you will know how to use all those endpoints.

## Prerequisites

Before you begin, ensure that you have:

- Registered your application with the `accounts` scope in the Developer Portal
- Obtained a sandbox/production `client_id` from the Developer Portal
- Obtained sandbox/production `transport.pem` and `signing.pem` certificates from the Developer Portal or from a QTSP issuing body
- Uploaded in the Developer Portal the [`jwks_url`](/docs/guides/build-banking-apps/register-your-application-using-dcr/get-the-jwks-url) that is specific to your `signing.pem` certificate

:::tip
If unsure, head to [Get Started](/docs/guides/build-banking-apps/get-started/register-your-application-in-the-developer-portal) and review the setup process.
:::

:::warning
If you get certificate errors when using `curl` with Sandbox, it usually means your system [doesn't trust our certificate issuer](/docs/guides/build-banking-apps/get-started/get-access-token#certificates). The recommended approach is to add the certificate to your trusted store.

As a quick workaround, you can use the `-k` (or `--insecure`) option to skip certificate checks. **Be aware this disables all SSL verification, which can hide issues like expired or mismatched certificates and leaves you vulnerable to man-in-the-middle attacks**. For these reasons, avoid using `-k` in production or as a permanent solution.
:::

## 1. Generate a client credentials token

Request an access token for client credentials using the `/token` endpoint and the `client_credentials` grant type:

- ![Production]

  ```shell
  curl --cert transport.pem --key private.key \
  --location --request POST 'https://oba-auth.revolut.com/token' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=client_credentials' \
  --data-urlencode 'scope=accounts' \
  --data-urlencode 'client_id=<your client_id>'
  ```

- ![Sandbox]

  ```shell
  curl --cert transport.pem --key private.key \
  --location --request POST 'https://sandbox-oba-auth.revolut.com/token' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=client_credentials' \
  --data-urlencode 'scope=accounts' \
  --data-urlencode 'client_id=<your client_id>'
  ```

**Response:**

The response returns a new client credentials access token, with its expiry (`expires_in`) provided in seconds:

```json
{
  "access_token":"<JWT client credentials>",
  "token_type":"Bearer",
  "expires_in": 2399
}
```

You use this token to:

- [Update your application](/docs/guides/build-banking-apps/manage-your-applications/update-an-application).
- [Create an account access consent](#2-create-an-account-access-consent) in the next step.

:::note
When your token expires and you need a new one, repeat this procedure to generate a new token.
:::

## 2. Create an account access consent

Create a consent for specific permissions using the `/account-access-consents` endpoint.

In this tutorial, you create a consent for the following permissions:

- `ReadAccountBasic`
- `ReadAccountsDetail`

[//]: # 'Tabs'

- ![Production]

  ```shell
  curl --cert transport.pem --key private.key \
  --location --request POST 'https://oba-auth.revolut.com/account-access-consents' \
  --header 'x-fapi-financial-id: 001580000103UAvAAM' \
  --header 'Authorization: Bearer <insert JWT client credentials from step 1.>' \
  --header 'Content-Type: application/json' \
  --data-raw '
  {
      "Data": {
          "Permissions": [
              "ReadAccountsBasic",
              "ReadAccountsDetail"
          ],
          "ExpirationDateTime": "2020-12-02T00:00:00+00:00",
          "TransactionFromDateTime": "2020-09-03T00:00:00+00:00",
          "TransactionToDateTime": "2020-12-03T00:00:00+00:00"
      },
      "Risk": {}
  }'
  ```

  **Response:**

  ```json
  {
    "Data": {
      "Status": "AwaitingAuthorisation",
      "StatusUpdateDateTime": "2020-11-05T16:07:46.506182Z",
      "CreationDateTime": "2020-11-05T16:07:46.506182Z",
      "TransactionToDateTime": "2017-12-03T00:00:00+00:00",
      "ExpirationDateTime": "2021-05-02T00:00:00+00:00",
      "Permissions": [
        "ReadAccountsBasic",
        "ReadAccountsDetail"
      ],
      "ConsentId": "d19ec758-22fd-4f34-9ad4-7437f8628987",
      "TransactionFromDateTime": "2017-05-03T00:00:00+00:00"
    },
    "Risk": {},
    "Links": {
      "Self": "https://oba-auth.revolut.com/account-access-consents"
    },
    "Meta": {
      "TotalPages": 1
    }
  }
  ```

- ![Sandbox]

  ```shell
  curl --cert transport.pem --key private.key \
  --location --request POST 'https://sandbox-oba-auth.revolut.com/account-access-consents' \
  --header 'x-fapi-financial-id: 001580000103UAvAAM' \
  --header 'Authorization: Bearer <insert JWT client credentials from step 1.>' \
  --header 'Content-Type: application/json' \
  --data-raw '
  {
      "Data": {
          "Permissions": [
              "ReadAccountsBasic",
              "ReadAccountsDetail"
          ],
          "ExpirationDateTime": "2020-12-02T00:00:00+00:00",
          "TransactionFromDateTime": "2020-09-03T00:00:00+00:00",
          "TransactionToDateTime": "2020-12-03T00:00:00+00:00"
      },
      "Risk": {}
  }'
  ```

  **Response:**

  ```json
  {
    "Data": {
      "Status": "AwaitingAuthorisation",
      "StatusUpdateDateTime": "2020-11-05T16:07:46.506182Z",
      "CreationDateTime": "2020-11-05T16:07:46.506182Z",
      "TransactionToDateTime": "2017-12-03T00:00:00+00:00",
      "ExpirationDateTime": "2021-05-02T00:00:00+00:00",
      "Permissions": [
        "ReadAccountsBasic",
        "ReadAccountsDetail"
      ],
      "ConsentId": "d19ec758-22fd-4f34-9ad4-7437f8628987",
      "TransactionFromDateTime": "2017-05-03T00:00:00+00:00"
    },
    "Risk": {},
    "Links": {
      "Self": "https://sandbox-oba-auth.revolut.com/account-access-consents"
    },
    "Meta": {
      "TotalPages": 1
    }
  }
  ```

:::note
If no explicit expiration date is set in the consent, it will remain valid for the refresh token's lifetime (180 days for the EU and 50 years for the UK). If a consent expiration date is defined, data access ends when either the token expires or the consent expires, whichever comes first.
:::

To check all the supported permissions and the dependencies:

[See the API reference: Create an account access consent](/docs/api/open-banking#create-an-account-access-consent)

## 3. Create a JWT URL parameter

After you create a consent, you need the user to authorise the consent so that you can access the data on their behalf.

Create a JWT request parameter with the header and body format presented below, signed by your signing certificate's private key.
This signature is validated using the JWKs endpoint that you specified during the registration of your application.

:::note [Header and payload parameters]
- The values of `client_id`, `redirect_uri`, `kid`, and `scope` should correspond to those for your specific application and consent request.
- The value of `openbanking_intent_id` is the value of the `ConsentId` field returned in the [consent creation response](#2-create-an-account-access-consent).
- `state` is an OAuth parameter that lets you restore the state of the application after redirection.
  If provided, this value is returned in the redirect URI.
- `nbf` and `exp` should contain the Unix timestamps before and after which the JWT is not valid.

For more details on these parameters, see the guide: [Work with JSON Web Tokens](/docs/guides/build-banking-apps/tutorials/work-with-json-web-tokens).
:::

**Header:**

```json
{
  "alg": "PS256",
  "kid": "<insert kid>"
}
```

**Body:**

```json
{
  "response_type": "code id_token",
  "client_id": "<insert client_id>",
  "redirect_uri": "<insert redirect_uri>",
  "aud": "https://oba-auth.revolut.com",
  "scope": "accounts",
  "state": "<insert state>",
  "nbf": <insert nbf>,
  "exp": <insert exp>,
  "claims": {
    "id_token": {
      "openbanking_intent_id": {
        "value": "<insert ConsentId>"
      }
    }
  }
}
```

## 4. Get consent from the user

Redirect the user to the authorisation URL with the following parameters. Make sure they are URL-encoded.

| Parameter       | Description                                                                                                                                                                                                                                                                                                          | Required |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `response_type` | Always set to `code id_token`.                                                                                                                                                                                                                                                                                       | yes      |
| `client_id`     | The client ID for your application.                                                                                                                                                                                                                                                                                  | yes      |
| `redirect_uri`  | One of the redirect URIs that you defined when you created the application. Keep it static without variable parameters (`https://example.com`) and pass any return information or additional parameter via the `state` parameter in the [JWT payload](#3-create-a-jwt-url-parameter), not inside the `redirect_uri`. | yes      |
| `scope`         | The scope that you are requesting, for example, `accounts` or `payments`. For accounts and transaction information, use the scope `accounts`.                                                                                                                                                                        | yes      |
| `request `      | The encoded JWT generated in the previous step.                                                                                                                                                                                                                                                                      | yes      |
| `response_mode` | If set to `fragment` parameters will be passed in fragment section of redirect URI. Otherwise, parameters are passed in URI query. Passing parameters in fragment is considered to be more secure.                                                                                                                   | no       |

A sample authorisation URL looks like this:

- ![Production]

  ```shell
  https://oba.revolut.com/ui/index.html?response_type=code%20id_token&scope=accounts&redirect_uri=<insert redirect URL>&client_id=<insert client_id>&request=<insert JWT from step 3.>
  ```

- ![Sandbox]

  ```shell
  https://sandbox-oba.revolut.com/ui/index.html?response_type=code%20id_token&scope=accounts&redirect_uri=<insert redirect URL>&client_id=<insert client_id>&request=<insert JWT from step 3.>
  ```

:::tip [Expected result]
Once you have redirected the user to the authorisation URL, they will need to provide their Revolut credentials and complete the payment authorisation.

After authorising the payment, the user will be redirected back to the redirect URL (`redirect_uri`) containing the authorisation code (`code`) as a URL parameter, as in the below example:

```text
https://example.com/?code=oa_sand_sPoyVs-oMhyR36j5N-ZEVLfK9rQWPNssgIQqsOFZQ-c&id_token=<JWT id_token>&state=example_state
```

Use this authorisation code in the [next step](#5-exchange-the-authorisation-code-for-an-access-token).
:::

:::warning
The `code` is valid only for two minutes.
:::

## 5. Exchange the authorisation code for an access token

After the user authorises your consent, you use the `/token` endpoint with `grant_type=authorization_code` to get the access token that you can use to access the **Accounts** and **Transactions** endpoints:

:::note
The value of `auth_code` is the authorisation code (`code`) generated in the [previous step](#4-get-consent-from-the-user).
:::

- ![Production]

  ```shell
  curl  --key private.key --cert transport.pem \
  --location --request POST 'https://oba-auth.revolut.com/token' \
  --header 'Content-Type:application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=authorization_code' \
  --data-urlencode 'client_id=<your client_id>' \
  --data-urlencode 'code=<insert auth_code>'
  ```

- ![Sandbox]

  ```shell
  curl  --key private.key --cert transport.pem \
  --location --request POST 'https://sandbox-oba-auth.revolut.com/token' \
  --header 'Content-Type:application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=authorization_code' \
  --data-urlencode 'client_id=<your client_id>' \
  --data-urlencode 'code=<insert auth_code>'
  ```

**Response:**

The response returns a new access token, with its expiry (`expires_in`) provided in **seconds**, and a refresh token, with its expiry (`refresh_token_expires_at`) provided as a **timestamp**.

:::note [Expiry]
- **Access token:**
  - Newly issued access tokens are valid for 60 minutes.
  - To obtain a new access token and extend (refresh) your access, [use the refresh token](#get-a-new-access-token).
  - A refreshed access token is valid for max. 60 minutes, but no longer than the refresh token that was used to generate it.
- **Refresh token:**
  - Due to the [Commission Delegated Regulation (EU) 2022/2360 of 3 August 2022](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=uriserv%3AOJ.L_.2022.312.01.0001.01.ENG&toc=OJ%3AL%3A2022%3A312%3ATOC), for Third Party Providers (TPPs) with an eIDAS certificate, for AIS consents, we issue refresh tokens valid for 180 days.
  - For UK-based AISPs, we issue long-lived refresh tokens valid for 50 years.
:::

A sample response looks like this:

```json
{
  "access_token":"oa_sand_tP1Nofi1ixsRfBmVBtVPdIVN0J5x91imqmheQIWTS5s",
  "access_token_id":"3d3ef8a1-f920-4d6d-9106-e7fcf350125c",
  "token_type":"Bearer",
  "expires_in":3599,
  "id_token":"<JWT id_token>",
  "refresh_token":"oa_sand_mV1NoTBtVPdfi1ixsRfBmS5sqmheQIWIVN0J5x91itP",
  "refresh_token_expires_at":1759051135
}
```

## 6. Get the list of accounts

Once you have a valid [access token](#5-exchange-the-authorisation-code-for-an-access-token), you can use it to send requests to the API. Make a call to the `/accounts` endpoint to retrieve a list of the user's accounts.

:::warning [Unique account identifier]
When retrieving multiple accounts, ensure you use the `Data.Account.AccountId` as a unique identifier and not `Data.Account.Account.Identification`.

`Identification` will typically contain an account number or an IBAN, but these can be the same across several currency subaccounts. Each of the subaccounts will always have a unique and immutable `AccountId`.
:::

- ![Production]

  ```shell
  curl --cert transport.pem --key private.key \
  -X GET https://oba-auth.revolut.com/accounts \
  --header 'Authorization: Bearer <insert access_token from step 5.>' \
  --header 'x-fapi-financial-id: 001580000103UAvAAM'
  ```

  **Response:**

  ```json
  {
    "Data": {
      "Account": [
        {
          "AccountId": "A1086696-D134-472D-B83E-A3F4D201C058",
          "Currency": "GBP",
          "AccountType": "Personal",
          "AccountSubType": "CurrentAccount",
          "Nickname": "Bills",
          "Account": [
            {
              "SchemeName": "UK.OBIE.SortCodeAccountNumber",
              "Identification": "80200110203345",
              "Name": "Mr Kevin",
              "SecondaryIdentification": "00021"
            }
          ]
        }
      ]
    },
    "Links": {
      "Self": "https://oba-auth.revolut.com/accounts/"
    },
    "Meta": {
      "TotalPages": 1
    }
  }
  ```

- ![Sandbox]

  ```shell
  curl --cert transport.pem --key private.key \
  -X GET https://sandbox-oba-auth.revolut.com/accounts \
  --header 'Authorization: Bearer <insert access_token from step 5.>' \
  --header 'x-fapi-financial-id: 001580000103UAvAAM'
  ```

  **Response:**

  ```json
  {
    "Data": {
      "Account": [
        {
          "AccountId": "A1086696-D134-472D-B83E-A3F4D201C058",
          "Currency": "GBP",
          "AccountType": "Personal",
          "AccountSubType": "CurrentAccount",
          "Nickname": "Bills",
          "Account": [
            {
              "SchemeName": "UK.OBIE.SortCodeAccountNumber",
              "Identification": "80200110203345",
              "Name": "Mr Kevin",
              "SecondaryIdentification": "00021"
            }
          ]
        }
      ]
    },
    "Links": {
      "Self": "https://sandbox-oba-auth.revolut.com/accounts/"
    },
    "Meta": {
      "TotalPages": 1
    }
  }
  ```

For more details about this call:

[See the API reference: Retrieve all accounts](/docs/api/open-banking#retrieve-all-accounts)

:::tip [Congratulations!]
You have successfully made your first API request to one of our Accounts and Transactions endpoints.
:::

## Usage of the Refresh Token

When the access token expires, you won't be able to access the **Accounts** and **Transactions** endpoints.

To regain access, you must first request a new access token using the refresh token (returned in [step 5](#5-exchange-the-authorisation-code-for-an-access-token)), your Client ID, and your certificate and key.

### Get a new access token

To request a new access token, make a request to the `/token` endpoint with `grant_type=refresh_token` and a valid refresh token:

:::warning [Refreshing an access token invalidates the previous access token.]

This means that if you have an access token that hasn't expired yet, and you refresh it with the refresh token, the existing access token will no longer work, and you will need to switch to the new access token.
:::

- ![Production]

  ```shell {6}
  curl --cert transport.pem --key private.key \
  --location --request POST 'https://oba-auth.revolut.com/token' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=refresh_token' \
  --data-urlencode 'client_id=<your client_id>' \
  --data-urlencode 'refresh_token=<your refresh_token>'
  ```

  **Response:**

  The response returns a refreshed access token, with its expiry (`expires_in`) provided in seconds:

  ```json {2}
  {
    "access_token":"oa_prod_fBmVQP91imqmhetP1NofixsBtVIVN0J5xRi1IdWTS5s",
    "token_type":"Bearer",
    "expires_in":3599
  }
  ```

- ![Sandbox]

  ```shell {6}
  curl --cert transport.pem --key private.key \
  --location --request POST 'https://sandbox-oba-auth.revolut.com/token' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=refresh_token' \
  --data-urlencode 'client_id=<your client_id>' \
  --data-urlencode 'refresh_token=<your refresh_token>'
  ```

  **Response:**

  ```json {2}
  {
    "access_token":"oa_sand_ixsdIVN0J5xRfBmVQBtVP91imqmhetP1Nofi1IWTS5s",
    "token_type":"Bearer",
    "expires_in":7775999
  }
  ```

:::note [Maximum expiry]
When refreshing an access token, its expiry will always be bound by the expiry of the refresh token.

This means that if you refresh an access token, but the refresh token is expiring in 10 minutes, you will obtain a new access token with an expiry of just 10 minutes instead of 60 minutes.

To regain access after the refresh token expires, you must repeat the steps 1-5 from this guide and [generate a new access token and refresh token](#1-generate-a-client-credentials-token).
:::

## What's next

- See the [Open Banking API reference](/docs/api/open-banking) for details on different endpoints and permissions.
- See [Tutorials: Initiate your first payment](/docs/guides/build-banking-apps/tutorials/initiate-your-first-payment) for building your integration to our payments endpoints.