# Building your German pension offering

## Prerequisites

Before opening an Altersvorsorgedepot and/or Standarddepot, a user must be created in the Upvest system. For existing eligible users Altersvorsorgedepot and/or Standarddepot accounts can be opened directly after adding additional data to the user model as outlined below.

## Enabling pension accounts via tax wrapper functionality

### 1. Onboarding: Creating new and updating existing users

Adding a user follows the usual user creation flow (see the [Users guide](/products/byol/guides/users) for more information). This includes verifying that the user is a tax resident of Germany. You can use the `POST /users/{user_id}/tax_residencies` endpoint to create the end user’s residency.

In addition, there are a series of fields that are required for both the Altersvorsorgedepot and the Standarddepot when applying for the government bonus. You can add the additional fields when completing the initial user onboarding or when updating existing users details.

If an end user provides these details after onboarding, or for other changes to user data, you can update the details for an existing user by submitting a user data change via the `POST /users/{user_id}/data_change_requests` endpoint as shown below.

When requesting user data changes, submit each request individually and wait for the `/user events` webhook confirmation before submitting the next request.

#### Example user fields for government bonus application

**`POST /users/{user_id}/data_change_requests`**

Gender

```json
{
"gender": "MALE"
}
```

Birth city

```json
{
"birth_city": "Berlin"
}
```

Social security number

```json
{
"social_security_number": "25300972S014"
}
```

The `birth_name` field is also required when the `last_name` is different.

### 2. Onboarding: Creating the Altervorsorgedepot and Standarddepot account groups

After creating or updating the user, the next step is the creation of an account group. You must create a separate account group for the Altervorsorgedepot and the Standarddepot accounts to enable contribution and bonus administration and to enable special tax treatment.

Set `type` equal to `DE_PENSION` when creating the account group:

#### Example creating a German pension account group

**`POST /account_groups`**

Request

```json
{
  "user_id": "413715f2-5401-4b97-8055-034a6b879f8c",
  "type": "DE_PENSION"
}
```

The account group will not transition to `ACTIVE` until the tax wrapper is `ACTIVE`.

After creating the account group, you can proceed with creating the tax wrapper and accounts.

### 3. Create the tax wrapper

The business rules and logic applicable to a tax-wrapped investment product are managed through the tax wrapper entity. The tax wrapper represents the entirety of the Altersvorsorgedepot or Standarddepot contract while the Upvest accounts are used only to separate out investment strategies within the pension. When creating the tax wrapper the type - either Altersvorsorgedepot (**AVD**) or **Standarddepot** - must be specified for each pension account.

In addition, the contract number is required to enable Upvest to process bonus applications.  The contract number can be generated by Upvest on tax wrapper creation, or provided by the client in the post. Retirement age defines when the payout period should begin.

##### Example creating a tax wrapper

**`POST  /de_pension/wrappers`**

Request

```json
{
  "type": "AVD",
  "account_group_id": "0d68fea2-66e8-4ea8-b507-276e7a1eb4aa",
  "retirement_age": "65",
  "contract_number": "XJ49-BN22-9901-PQ88"
}
```

Response

```json
{
  "tax_wrapper_id": "019999a3-c02a-7043-b100-9c2d76564748",
  "retirement_age": "65",
  "contract_number": "XJ49-BN22-9901-PQ88",
  "type": "AVD"
}
```

When configuring the tax wrapper, you can set `type` to either `AVD` or `STANDARDDEPOT` depending on the use case.

| **Field** | **Description** |
|  --- | --- |
| `type` | The selection of either `AVD` or `Standarddepot` pension account. |
| `account_group_id` | The UUID generated when creating the account group. |
| `retirement_age` | The age the end user would like to move to the payout phase. Clients can choose to default to an age set out in the contract or selected by the end user.For new users, this value must be between 65-70. |
| `contract_number` | The specific identifier for the Altersvorsorgedepot or Standarddepot. |
| `tax_wrapper_id` | The unique identifier for the tax wrapper. |


After a tax wrapper is created, it remains inactive until all activation requirements are fulfilled:

- The user must be in `ACTIVE` status and complete a [tax residency assessment](/products/byol/guides/users/users_tax_onboarding).


The tax wrapper status changes to `ACTIVE` once these conditions are satisfied. Following tax wrapper activation, the account group and accounts are automatically activated and ready to receive contributions.

### 4. Create the account

Create an account as described in the [Create Accounts](/products/byol/guides/accounts/accounts_create_accounts) guide.

##### Example request to create an account

**`POST /accounts`**

Request

```json
{
  "user_id": "413715f2-5401-4b97-8055-034a6b879f8c",
  "account_group_id": "0d68fea2-66e8-4ea8-b507-276e7a1eb4aa",
  "type": "PORTFOLIO",
  "name": "Main account"
}
```

When creating the account, you can set `type` to either `TRADING` or `PORTFOLIO` depending on the use case.

### 5. Allowance events

Once the tax wrapper has been successfully created and activated, the Investment API will automatically create the underlying Allowance resource for the current tax year and confirm its creation via the `DE_PENSION_WRAPPER` and `DE_PENSION_WRAPPER_ALLOWANCE` webhooks.

The allowance entity tracks all user contributions to help track the minimum amount for the bonus application and prevents contributions exceeding the maximum contribution amount allowed per tax year. Once the contribution amount reaches the limit, additional contributions are automatically rejected.

Since users are not allowed to carry over remaining allowance amounts from prior years, the Investment API automatically closes the completed year and creates a new allowance each year.

You can view a user’s allowance using the `GET /de_pension/wrappers/{tax_wrapper_id}/allowances/` endpoint. This provides access to the amount of allowance available to the user.

##### Example response for displaying allowances

**`GET /de_pension/wrappers/{tax_wrapper_id}/allowances/`**

Response

```json
{
 "meta": {
   "offset": 0,
   "limit": 100,
   "count": 2,
   "total_count": 2,
   "sort": "valid_from",
   "order": "DESC"
 },
 "data": [
   {
     "allowance_id": "019999a3-8a17-74e6-bfbd-50d9c7705ea0", 
     "tax_wrapper_id": "019999a3-c02a-7043-b100-9c2d76564748",
     "tax_year": "2026",
     "type": "ANNUAL",
     "status": "ACTIVE",
     "currency": "EUR",
     "total_amount": "6840.00",
     "used_amount": "0.00",
     "remaining_amount": "6840.00",
     "created_at": "2026-07-21T14:10:00.00Z",
     "updated_at": "2026-07-21T14:10:00.00Z",
     "valid_from": "2026-07-21T14:10:00.00Z",
     "valid_to": "2026-12-31T23:59:59.00Z"
   },
   {
     "allowance_id": "019999a3-8a17-74e6-bfbd-50d9c7705ea0", 
     "tax_wrapper_id": "019999a3-c02a-7043-b100-9c2d76564748",
     "tax_year": "2025",
     "type": "ANNUAL",
     "status": "EXPIRED",
     "currency": "EUR",
     "used_amount": "0.00",
     "remaining_amount": "6840.00",
     "created_at": "2025-07-21T14:10:00.00Z",
     "updated_at": "2025-07-21T14:10:00.00Z",
     "valid_from": "2025-07-21T14:10:00.00Z",
     "valid_to": "2025-12-31T23:59:59.00Z"
   }
 ]
}
```

| **Field** | **Description** |
|  --- | --- |
| `allowance_id` | The UUID created for each annual allowance. |
| `tax_year` | The year of the allowance. |
| `type` | The `ANNUAL` type refers to how the regulatory contribution limit renews annually.Regulatory changes to this limit are managed by Upvest and will automatically be applied before the start of the tax year. |
| `status` | This field may contain the following values:- `ACTIVE`: the allowance is fully enabled and the user can make additional contributions up to the value of the **remaining_amount**.- `EXPIRED`: the allowance is outside of the `valid_from` and `valid_to` dates. The end user can no longer make contributions.Once an allowance entity with the type `ANNUAL` hits the `valid_to` date, it will transition to the status `EXPIRED`. Expired allowances do not roll over. New allowance entities will be created at commencement of the new tax year. |
| **Allowance values** |  |
| `used_amount` | The amount of contributions submitted so far. |
| `remaining_amount` | The calculated difference between the annual allowance and the `used_amount`, indicating how much remaining allowance is available. |
| `valid_from` and `valid_to` | Shows the period in which the allowance is in effect.Normally, these values equal the first and last date of the year. |