# Connect a clinic from your system

> Start a clinic's way into Rivr from your own code, show the link in your own interface and follow the status until the clinic can take payments.

A clinic that uses your journal system wants to start taking payments with Rivr. Instead of sending it to Rivr's website you start the onboarding from your own code: `POST /api/public/onboarding-links` gives you a link that you show to a signed-in clinic user. The clinic signs the agreement with BankID, Rivr and the payment provider review it, and you follow the status with `GET` until `chargesEnabled` turns true. The clinic is then a merchant under your platform and you can start sending orders.

This is the way for a clinic without a Rivr account. A clinic that already has one is connected with OAuth instead (the sandbox first, production when Rivr confirms), and the guide Connect a clinic that already has a Rivr account shows how. If you get `error=account_required` back from that way, you start here.

- Market: Sweden (`se`). Norway differs in currency, payment methods and what happens when nobody pays: https://developers.rivr.io/guides/onboarding-links.md?market=no
- Language: English (`en`). Svenska: https://developers.rivr.io/guides/onboarding-links.md?lang=sv
- 6 min read · 6 steps
- Page in the portal: https://developers.rivr.io/guides/onboarding-links

Every example targets the sandbox (`https://api.test.rivr.io`) and needs test credentials. A platform client's tokens come from `https://auth.test.rivr.io`, an organisation key's from `https://auth.test.rivr.tech`.

## 1. Rivr switches the feature on for your platform

*Rivr → Journal system*

Onboarding links are not something you switch on yourselves. Rivr switches them on per platform and decides which price lists your platform may use. If they are not on, `POST` answers `403` with `code` `onboarding_not_enabled`. Only `POST` checks that they are on.

The calls also need a **platform token** (`grant_type: client_credentials`) with the scope `public:onboarding:write` to create and refresh and `public:onboarding:read` to read. Rivr sets the scopes on your client. Without the scope all four calls answer `403` with `error: "invalid_scope"`, before anything else. If the token has the scope but carries no platform, such as a merchant token (it carries the client's scope but never the platform), all four calls answer `401` with `code` `platform_token_required`. That includes `GET /onboarding-links/{id}` and `refresh`, which therefore do not answer `404`.

**What your system does:** Ask your Rivr contact to switch the feature and the scopes on, for your test platform first. Then get a platform token as in the guide Connect as a platform.

> **Note.** The controller's own errors, such as the `403` and `400` described here, are the error object in `application/json`, not `application/problem+json`: `code` and `type` sit next to `message`, which is in English. Program against `code`, never against the text. Each code has a page of its own under Error codes, and the onboarding link codes are collected under Onboarding links in /api/public.

**In the portal:** [See the error codes for onboarding links](https://developers.rivr.io/errors#errors-onboarding)

## 2. Start the onboarding

*Journal system → Rivr*

Send the clinic's organisation number, Swedish or Norwegian, with or without a hyphen. It is the only thing required. The rest is pre-fill that shortens the clinic's way: `companyName` (only for your own recognition in the list), `contactName`, `email` and `phone`, and `priceListId` if your platform has more than one price list. Leave it out and the clinic gets your default.

The response is the onboarding: `id` (your stable key, store it), `url` (the link the clinic should open), `expiresAt` (14 days ahead) and `status`. The status code is `200`, never `201`, also for a new onboarding. If you already have an onboarding for the same organisation number that can still be opened you get that one back, also with `200`, instead of a new one. Clicking a button twice therefore doesn't give the clinic two agreements, but the response looks the same: recognise a reused one by its `id`.

**Why:** What you send in never decides who may sign. That is decided by the signatory rights in the company register and by a BankID signature. What you send is only pre-fill, and the agreement is always written on the registered name.

**What your system does:** Store the `id` on the clinic in your system before you do anything else. It is the key for every status call, and it survives the clinic changing its email or getting a new link.

> **Warning.** `400` means the onboarding was not started: the organisation number is not valid, a field is too long, `sendEmail` is set without `email`, the price list is not one your platform may use, or the price list is in another currency than the clinic's market. The codes are `organisation_number_invalid`, `field_too_long`, `send_email_requires_email`, `price_list_not_allowed` and `price_list_wrong_market`. A sole proprietorship's organisation number is the owner's personal identity number. Treat it as personal data in your logs.

**Request:** `POST /api/public/onboarding-links` · Platform token · [Reference](https://developers.rivr.io/reference/api-public/createOnboardingLink.md)

```bash
curl -X POST https://api.test.rivr.io/api/public/onboarding-links \
  -H "Authorization: Bearer $PLATFORM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "organisationNumber": "5566778899",
  "companyName": "Exempelkliniken AB",
  "contactName": "Kontaktperson",
  "email": "reception@example.com",
  "sendEmail": false
}'
```

**Response (excerpt)**

```json
{
  "id": "0b8e6f3c-5d2a-4e71-9c4b-7a1f2d3e4c5b",
  "organisationNumber": "5566778899",
  "companyName": "Exempelkliniken AB",
  "url": "https://…",
  "expiresAt": "2026-11-06T09:30:00+00:00",
  "status": "pending",
  "requirements": [],
  "chargesEnabled": false,
  "payoutsEnabled": false,
  "merchantId": null,
  "createdAt": "2026-10-23T09:30:00+00:00",
  "signedAt": null,
  "emailSent": false
}
```

## 3. Show the link to the clinic

*Journal system → Clinic*

Show `url` in your own interface to a signed-in clinic user. That converts better than an email, and keeps the link out of mailboxes. Don't email it on yourselves.

If you would rather have Rivr email the clinic, set `sendEmail` to `true` and send `email` along. Rivr's usual cadence then applies: a first link and reminders every fifth day, at most two.

The link is valid for 14 days and stops working at `expiresAt`. `POST /api/public/onboarding-links/{id}/refresh` gives a new link: the same `id`, the same pre-fill, another 14 days. It does not work on an onboarding that is signed or revoked, and answers `409` with `onboarding_already_signed` or `onboarding_link_revoked`. If the id isn't at your platform, `refresh` and `GET` answer `404` with `onboarding_link_not_found`. If the list on the onboarding can't be used any more, `refresh` answers `400` with `price_list_not_allowed` or `price_list_wrong_market`, and no link is refreshed. Contact Rivr then. If a link can't be issued at the moment, `POST` and `refresh` answer `400` with `onboarding_link_not_issued`. After a `POST` the onboarding may already exist as a draft without a link: list your onboardings and get the draft's link with `refresh` instead of sending the same `POST` again (see the code's page). `refresh` can be retried a while later.

**What your system does:** Fetch the link with `GET` when the clinic is about to open it, instead of storing it: `url` is `null` when the onboarding is signed, expired or revoked. If it has expired: refresh.

**Request:** `POST /api/public/onboarding-links/ONBOARDING_LINK_ID/refresh` · Platform token · [Reference](https://developers.rivr.io/reference/api-public/refreshOnboardingLink.md)

```bash
curl -X POST https://api.test.rivr.io/api/public/onboarding-links/ONBOARDING_LINK_ID/refresh \
  -H "Authorization: Bearer $PLATFORM_TOKEN"
```

## 4. The clinic signs with BankID

*Clinic → Rivr*

The clinic opens the link and goes through Rivr's sign-up flow. The person with signatory rights signs the agreement with BankID. `status` goes from `pending` (the link is issued) to `signed`. `opened` only comes when Rivr emails the link (`sendEmail`) and sees the email opened or the link clicked. If you show the link yourselves, the status goes straight from `pending` to `signed`.

The clinic doesn't pick a journal system in the flow. Once the agreement is signed it exists as a merchant under your platform. The onboarding then has a `merchantId`, and the clinic shows up in `GET /api/public/merchants`. With that id you get a merchant token as in the guide Connect as a platform.

In three cases the clinic picks the system itself: a dormant clinic that is reactivated and already stands under another system or none, a clinic whose onboarding you got back from the platform portal (Send agreement) instead of a new one, and a clinic whose link was created before Rivr switched off onboarding links for your platform. If `merchantId` isn't in `GET /api/public/merchants` after the signing, the clinic has ended up under another system or none. If it stands without a platform, it can be connected with the account connection.

> **Warning.** In the sandbox the signing goes as in production: a signatory signs with BankID. We have not documented any way to sign as an invented person. Try `POST`, `refresh` and reading the status in the sandbox, and plan with Rivr how you test the whole way to a clinic that can take payments.

## 5. Follow the status with GET

*Journal system → Rivr*

`GET /api/public/onboarding-links/{id}` gives the onboarding's current state, and `GET /api/public/onboarding-links` lists all of yours, newest first (`limit`, default 100, at most 1,000). It is the polling path, and the answer is always right.

`status` is one of: `pending`, `opened` (only when Rivr emails the link), `signed` (the agreement is signed and the merchant exists, the Adyen setup is under way), `in_review` (Adyen is reviewing or waiting for something in `requirements`; it is where a clinic stays longest, since bank account proof is reviewed manually), `active` (the Adyen onboarding is done), `expired` (the link ran out, `refresh` gives a new one) and `revoked` (revoked, one way: create a new one).

`requirements` says in plain words what is missing before the clinic can take payments, in the clinic's language, but never which person or which document it concerns. The list is empty when nothing is waiting.

**What your system does:** Show the status in your interface, and show `requirements` to the clinic's user. Poll calmly: a clinic in review stays there for days, so a call a minute is unnecessary. The rate limits are under Error codes.

> **Warning.** `GET /api/public/onboarding/progress` is something else. It is the checklist for your own integration, with steps such as getting a token and listing clinics, and needs the scope `public:health:read`. It says nothing about an individual clinic. Use `onboarding-links` for the clinic.

**Request:** `GET /api/public/onboarding-links/ONBOARDING_LINK_ID` · Platform token · [Reference](https://developers.rivr.io/reference/api-public/getOnboardingLink.md)

```bash
curl -X GET https://api.test.rivr.io/api/public/onboarding-links/ONBOARDING_LINK_ID \
  -H "Authorization: Bearer $PLATFORM_TOKEN"
```

## 6. chargesEnabled: the clinic can take payments

*Rivr → Journal system*

**`chargesEnabled` is the signal to start sending orders on `merchantId`, not `status`.** `active` only means the payment provider's review is done. Rivr may still be reviewing the details, and then it says `requirements: ["Rivr granskar uppgifterna"]` (in Norwegian for a Norwegian clinic) and `chargesEnabled` is false. The reason it is false is never stated.

The flag follows the same rule Rivr's payment paths use to let a payment through, so an order created while it is true does not bounce because the clinic is not ready. If Rivr cannot decide at the moment it is false, never true on a guess. Before signing, and for a revoked onboarding, it is always false.

`payoutsEnabled` is information that payouts to the clinic's bank account are allowed and verified. It does not decide which orders you can send.

**What your system does:** Start sending orders for the clinic only once `chargesEnabled` is true. Get a merchant token for `merchantId` and continue with the guide Payment at the clinic.

> **Note.** There is no event you can switch on yourselves to learn when the clinic can take payments: poll `GET`. On request Rivr can point a notification, with `type` `MerchantOnboarding`, at an https address of yours. Nothing is sent before the signing. The first notification is `signed` with `merchantId`, once the agreement is signed, and after that a new one comes when the state changes. The body is `{id, type, merchantId, status, data}`. `id` and `data.onboardingLinkId` are the onboarding's `id`, and `data` carries `status`, `previousStatus`, `requirements`, `chargesEnabled`, `payoutsEnabled`, `merchantId` and `organisationNumber`. A sole proprietorship's organisation number is the owner's personal identity number, so don't log it. The notification goes through the same delivery engine as order webhooks. A change of `chargesEnabled` can arrive hours after the clinic became ready, since Rivr goes through the states again after the nightly validation. It is an add-on, never the truth: a notification that doesn't come costs you nothing, because `GET` always answers right.

**Response (excerpt)**

```json
{
  "id": "0b8e6f3c-5d2a-4e71-9c4b-7a1f2d3e4c5b",
  "organisationNumber": "5566778899",
  "companyName": "Exempelkliniken AB",
  "url": null,
  "expiresAt": null,
  "status": "active",
  "requirements": [],
  "chargesEnabled": true,
  "payoutsEnabled": true,
  "merchantId": "aa2225b7-de35-4afa-bd9b-8a47577ce844",
  "createdAt": "2026-10-23T09:30:00+00:00",
  "signedAt": "2026-10-23T09:41:12+00:00",
  "emailSent": false
}
```

## Common pitfalls

- Starting an onboarding for a clinic that already has a Rivr account. Connect it with OAuth instead (the sandbox first, production when Rivr confirms). An onboarding is for a clinic without an account. The connection's `error=account_required` means the person has said the clinic has no account, not that Rivr has checked it: if the clinic is already a customer it is told so in Rivr's flow, and the onboarding never becomes `signed`.
- Treating `status: "active"` as the clinic being able to take payments. Look at `chargesEnabled`.
- Calling with a token that does not carry a platform, such as a merchant token, or with a platform token that lacks the scope `public:onboarding:write` or `public:onboarding:read`. Without the scope every call gives `403` with `invalid_scope`. If the token has the scope but no platform, all four calls give `401` with `platform_token_required`, also `GET` on a single id and `refresh`.
- Reading `GET /api/public/onboarding/progress` as the clinic's state. It is the checklist for your own integration. The clinic's state is on the onboarding: `GET /api/public/onboarding-links/{id}`.
- Storing `url` and showing it later. It expires at `expiresAt` and is `null` once the onboarding is signed. Read the onboarding with `GET` when the link is to be shown, and renew with `refresh`.
- Starting a new onboarding for a clinic that already has an open one. The same organisation number gives you the open one back, with `200`. Store the `id` and reuse it.
- Counting on your organisation's own API keys (the API keys page in the portal) reaching the clinic by themselves. The clinic ends up under your platform, so the platform's `client_id` and `client_secret` reach it. The link between the platform and your organisation for the own keys is not made automatically yet.

## Next guide

- [Connect as a platform](https://developers.rivr.io/guides/connect.md): One set of credentials for the whole journal system, one token per clinic.
- [Connect a clinic that already has a Rivr account](https://developers.rivr.io/guides/connect-clinic-account.md): The clinic signs in at Rivr with BankID and approves the connection to your system, and you get the clinic's id through an OAuth 2.0 code exchange. The sandbox first, production when Rivr confirms.
- [Payment at the clinic](https://developers.rivr.io/guides/pay-at-clinic.md): The patient checks out at reception and pays on the device before leaving.
