# POST /api/public/subscriptions

> Creates or updates a subscription

**Retrying safely.** This endpoint creates the subscription the first time and adds payments to it after that, so a
request that is sent twice can start the payments twice. Send an `Idempotency-Key` header (a new UUID for every
subscription request) to make a retry safe, also when two requests arrive at the same time. The key must be a random
value and must not contain personal data. It is scoped to your API client (and the merchant, when the token is
issued for one; or the merchant alone, for a token issued with merchant credentials only) and to this endpoint. The
first response with a 2xx status is stored for 24 hours, and a request with the same key and the same body gets that
response again, with the header `Idempotent-Replayed: true`, instead of a second creation or extension. A response
that refused the request (4xx) is not stored, so the same key can be retried once the cause is gone. With the header,
the response can also carry these `errorCode` values, always with `propertyName` `Idempotency-Key`:

- **400** `idempotency_key_invalid`: the header is not a single value of 1 to 255 printable ASCII characters, or
the token identifies neither an API client nor a merchant. Nothing was done.
- **409** `idempotency_in_progress`: a request with the same key is still being processed. Retry shortly with the
same key. If Rivr was interrupted while processing it, this answer can last for up to 24 hours; if it persists
for more than a few minutes, check the subscription before you retry with a new key.
- **422** `idempotency_mismatch`: the key was already used for another body. Nothing was done. Use a new key.
- **500** `idempotency_outcome_unknown`: the request failed after the subscription may have been created or
extended. The same key gives this answer again for 24 hours. Check whether the subscription exists before you
retry with a new key.
- **503** `idempotency_unavailable`: the key could not be checked, and nothing was done. Retry with the same key.

Without the header the endpoint behaves exactly as before.

- Surface: `/api/public` · operationId `createSubscription` · Group: Subscription
- Authorisation: Token with scope `public:subscriptions:write`
- Organisation key: needs the capabilities `orders:manage` + `customers:read` on the clinic, all together
- The spec: https://developers.rivr.io/openapi/api-public.json
- The spec's introduction: https://developers.rivr.io/reference/api-public.md
- Page in the portal: https://developers.rivr.io/reference/api-public/createSubscription

The examples target the sandbox (`https://api.test.rivr.io`) and need test credentials.

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | no | Optional. A key of 1 to 255 printable ASCII characters, unique for every subscription request (a UUID is recommended). Retrying with the same key and the same request returns the stored response instead of creating or extending the subscription again. Stored for 24 hours. |

## Request body (`application/json`, optional)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `merchantId` | string (uuid) | – |  |
| `personalNumber` | string | – | (nullable) |
| `phone` | string | – | (nullable) |
| `email` | string | – | (nullable) |
| `subscriptionPayments` | PublicApiSubscriptionPayment[] | – |  |
| `subscriptionPayments[].createdDate` | string (date-time) | – |  |
| `subscriptionPayments[].dueDate` | string (date-time) | – |  |
| `subscriptionPayments[].amount` | number (double) | – |  |
| `subscriptionPayments[].description` | string | – | (nullable) |
| `subscriptionPayments[].externalReference` | string | – | (nullable) |

**Example**

```json
{
  "merchantId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "personalNumber": "195001182046",
  "phone": "+46701740605",
  "email": "kontakt@example.com",
  "subscriptionPayments": [
    {
      "createdDate": "2026-10-18T09:00:00Z",
      "dueDate": "2026-10-18T09:00:00Z",
      "amount": 0,
      "description": "string",
      "externalReference": "string"
    }
  ]
}
```

## Responses

### 201

The subscription was created, or an existing one got the payments in the request: its id and number. `Location` is
the address that reads it.

`application/json`

**Response fields**

| Field | Type | Description |
| --- | --- | --- |
| `subscriptionId` | string (uuid) |  |
| `subscriptionNumber` | string | (nullable) |

**Example**

```json
{
  "subscriptionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "subscriptionNumber": "string"
}
```

### 400

A rule in Rivr refused the subscription, for example a person who cannot be identified: `request_rejected`, with the
reason in `message`. With an Idempotency-Key header also `idempotency_key_invalid`. A request that cannot be read is rejected before the operation runs, with a validation problem instead: malformed JSON, a value of the wrong type, an empty body or a `null` required field. It carries the `code` `request_invalid`, `type`, `message`, `title`, `status`, `errors` (the messages per field) and `traceId`.

Error codes: [request_invalid](https://developers.rivr.io/errors/request_invalid.md), [request_rejected](https://developers.rivr.io/errors/request_rejected.md), [idempotency_key_invalid](https://developers.rivr.io/errors/idempotency_key_invalid.md)

`application/json`

```json
{
  "propertyName": "Idempotency-Key",
  "message": "The Idempotency-Key header must be a single value of 1 to 255 printable ASCII characters. Nothing was done.",
  "errorCode": "idempotency_key_invalid",
  "code": "idempotency_key_invalid",
  "type": "https://developers.rivr.io/errors/idempotency_key_invalid"
}
```

### 401

The token is missing (`token_missing`), not valid (`token_invalid`), expired (`token_expired`) or could not be
checked (`authentication_unavailable`), or it is not issued for a merchant (`merchant_token_required`).

Error codes: [token_missing](https://developers.rivr.io/errors/token_missing.md), [token_invalid](https://developers.rivr.io/errors/token_invalid.md), [token_expired](https://developers.rivr.io/errors/token_expired.md), [authentication_unavailable](https://developers.rivr.io/errors/authentication_unavailable.md), [merchant_token_required](https://developers.rivr.io/errors/merchant_token_required.md)

`application/json`

```json
{
  "title": "Unauthorized",
  "status": 401,
  "detail": "Invalid Authorization Header",
  "propertyName": null,
  "message": "The request has no valid Authorization header. Send the token in the Authorization header, after Bearer.",
  "errorCode": "token_missing",
  "code": "token_missing",
  "type": "https://developers.rivr.io/errors/token_missing"
}
```

### 403

The token lacks the scope (`invalid_scope`) or is a rivr-auth token that is not a clinic token
(`clinic_token_required`). The body also carries `error` and `error_description`, as before.

Error codes: [invalid_scope](https://developers.rivr.io/errors/invalid_scope.md), [clinic_token_required](https://developers.rivr.io/errors/clinic_token_required.md)

`application/json`

```json
{
  "error": "invalid_scope",
  "error_description": "The scope(s) 'public:subscriptions:write' is required.",
  "propertyName": null,
  "message": "The scope(s) 'public:subscriptions:write' is required.",
  "errorCode": "invalid_scope",
  "code": "invalid_scope",
  "type": "https://developers.rivr.io/errors/invalid_scope"
}
```

### 409

With an Idempotency-Key header: `idempotency_in_progress`. Without the header the endpoint does not return 409.

Error codes: [idempotency_in_progress](https://developers.rivr.io/errors/idempotency_in_progress.md)

`application/json`

```json
{
  "propertyName": "Idempotency-Key",
  "message": "A request with the same Idempotency-Key is still being processed. Retry shortly with the same key to get its response. If Rivr was interrupted while processing it, this answer can last for up to 24 hours. If it persists for more than a few minutes, check the order or subscription before you retry with a new key.",
  "errorCode": "idempotency_in_progress",
  "code": "idempotency_in_progress",
  "type": "https://developers.rivr.io/errors/idempotency_in_progress"
}
```

### 415

The body is not sent as `application/json`: `unsupported_media_type`.

Error codes: [unsupported_media_type](https://developers.rivr.io/errors/unsupported_media_type.md)

`application/json`

```json
{
  "type": "https://developers.rivr.io/errors/unsupported_media_type",
  "title": "Unsupported Media Type",
  "status": 415,
  "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-00",
  "code": "unsupported_media_type",
  "message": "The body's Content-Type is not one the operation reads. Send the body as application/json."
}
```

### 422

The merchant is a Rivr sandbox merchant that accepts only test identities, and the personal number is not one:
`errorCode` and `code` are `personal_number_not_test_identity`, with `propertyName` `PersonalNumber`. Nothing was
created. Use one of Skatteverket's published test personal numbers, one of Avarda's documented test persons or a
synthetic Norwegian identity number (a reserved test person, month + 65, or a Test-Norge or Tenor number, month + 80).
With an Idempotency-Key header also `idempotency_mismatch`.

Error codes: [personal_number_not_test_identity](https://developers.rivr.io/errors/personal_number_not_test_identity.md), [idempotency_mismatch](https://developers.rivr.io/errors/idempotency_mismatch.md)

`application/json`

```json
{
  "propertyName": "PersonalNumber",
  "message": "The merchant is a Rivr sandbox merchant and accepts only test identities: Skatteverket's published test personal numbers, Avarda's documented test persons and synthetic Norwegian identity numbers (reserved test persons, month + 65, and Test-Norge or Tenor numbers, month + 80). The personal number is not one of them, and nothing was created.",
  "errorCode": "personal_number_not_test_identity",
  "code": "personal_number_not_test_identity",
  "type": "https://developers.rivr.io/errors/personal_number_not_test_identity"
}
```

### 500

With an Idempotency-Key header: `idempotency_outcome_unknown`; the same key gives this answer again for 24 hours. Without the header: `internal_error`, something unexpected failed. In both cases check whether the subscription exists before you retry.

Error codes: [idempotency_outcome_unknown](https://developers.rivr.io/errors/idempotency_outcome_unknown.md), [internal_error](https://developers.rivr.io/errors/internal_error.md)

`application/json`

```json
{
  "propertyName": "Idempotency-Key",
  "message": "The request failed after the change may have been made, so its outcome is unknown. The request is not repeated for this Idempotency-Key. Check the order or subscription before you retry with a new key.",
  "errorCode": "idempotency_outcome_unknown",
  "code": "idempotency_outcome_unknown",
  "type": "https://developers.rivr.io/errors/idempotency_outcome_unknown"
}
```

### 503

With an Idempotency-Key header: `idempotency_unavailable`. Without the header the endpoint does not return 503 itself.

Error codes: [idempotency_unavailable](https://developers.rivr.io/errors/idempotency_unavailable.md)

`application/json`

```json
{
  "propertyName": "Idempotency-Key",
  "message": "The Idempotency-Key could not be checked right now, so nothing was done. Retry the request with the same key.",
  "errorCode": "idempotency_unavailable",
  "code": "idempotency_unavailable",
  "type": "https://developers.rivr.io/errors/idempotency_unavailable"
}
```

## Example request

```bash
curl -X POST https://api.test.rivr.io/api/public/subscriptions \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "merchantId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "personalNumber": "195001182046",
  "phone": "+46701740605",
  "email": "kontakt@example.com",
  "subscriptionPayments": [
    {
      "createdDate": "2026-10-18T09:00:00Z",
      "dueDate": "2026-10-18T09:00:00Z",
      "amount": 0,
      "description": "string",
      "externalReference": "string"
    }
  ]
}'
```
