# Connect a clinic that already has a Rivr account

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

A clinic that already has a Rivr account wants to use your journal system. An organisation number doesn't say that the person clicking has a right to the clinic's account, so matching on it isn't enough. Instead you send the browser to Rivr. The person signs in with BankID, picks the clinic and approves. Rivr sends the browser back to your registered address with a code, and you exchange the code for the clinic's id with your own secret. This is OAuth 2.0 authorization code (RFC 6749 §4.1) with PKCE.

**The sandbox first, production when Rivr confirms.** The connection is off for every platform until Rivr switches it on, and Rivr tells you when it can be used in production.

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

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. A connection or an onboarding link?

*Journal system → Rivr*

**The connection** is for a clinic that already has a Rivr account. The person signs in at Rivr and approves, and you get the clinic's id.

**The onboarding link** is for a clinic without a Rivr account. You start the onboarding with `POST /api/public/onboarding-links`, and the clinic signs the agreement with BankID. The guide Connect a clinic from your system shows how. The clinic then doesn't pick a journal system in Rivr's flow but ends up under your platform, so no connection is needed afterwards. The exceptions are in that guide.

If you don't know which applies, start with the connection. If onboarding links are switched on for your platform and your client has the scope `public:onboarding:write`, the person can choose **We have no Rivr account** on Rivr's page, and you get `error=account_required` back. Otherwise the page shows the way to Rivr.

**A clinic on another system doesn't switch by itself in this version.** It can't be picked on Rivr's page. A switch is made by Rivr, so ask the clinic to contact Rivr.

**What your system does:** Never let an organisation number on its own tie a clinic to an organisation at your end. `GET /api/public/merchants?searchText=` is a lookup, no proof that the person clicking has a right to the account. If you have built the connection on it, switch to the flow here.

**In the portal:** [The guide on onboarding links](https://developers.rivr.io/guides/onboarding-links)

## 2. Rivr registers your address and switches the connection on

*Rivr → Journal system*

You use your existing platform client: the same `client_id` and `client_secret` as in the guide Connect as a platform. Rivr does two things for you, once per environment.

**The address you are sent back to is registered on the client.** It is compared exactly, character for character. No wildcards and no addresses per clinic. `https` is required, and in the sandbox only, `http://localhost` and `http://127.0.0.1` work too. A client with no registered address is refused with `invalid_redirect_uri`.

**The connection is switched on for your platform.** Until then you get `error=temporarily_unavailable` back from `/oauth/authorize`.

The client must also belong to a platform and carry only scopes under `public:*`, and the platform must have no users of its own in Rivr's platform portal. Otherwise the client is refused with `invalid_client` on Rivr's error page.

**What your system does:** Send the address per environment to your contact at Rivr. Give it a path of its own for Rivr, for example `/rivr/callback`, so that it isn't mixed up with another provider's.

> **Warning.** The sandbox first, production when Rivr confirms. The addresses in the examples are the sandbox's. Production's are the same without `.test`, and production's client is another one, with its own registered address.

## 3. Create state and PKCE for every attempt

*Journal system*

Before the browser is sent to Rivr your system creates two random values, new for every attempt.

**`state`** ties the answer to whoever started. Draw 32 random bytes, and store the value on the server with the organisation, the user and an expiry of 15 minutes. It must be **single use**, **bound to the session** and **checked before the code exchange**. Rivr accepts only 32 to 512 characters from `A-Z a-z 0-9 . _ ~ -`.

**PKCE with `S256` is required**, on top of the client's secret. `code_verifier` is 43 to 128 characters, and `code_challenge` is its SHA-256 in base64url, that is exactly 43 characters. The challenge goes to Rivr now, and the verifier stays with you until the code exchange.

**Create the pair when the person clicks,** in a server route that answers with a redirect to Rivr, not when the page renders. **An attempt belongs to one browser:** if the same `state` or the same `code_challenge` is opened in another browser, that browser stays on Rivr's error page with `started_in_another_browser`. The same browser may reload (in an older browser an interrupted first answer can still give the error page, and then the person starts over with you). A test run without a browser therefore needs a new pair for every attempt.

If the answer comes to another host than the one holding the session, set a short-lived cookie on that host before the browser is sent to Rivr (`HttpOnly`, `Secure`, `SameSite=Lax`) and compare it when the browser comes back.

**Why:** This stops OAuth CSRF. Without the binding an attacker can get a valid code for their own clinic and trick a signed-in user of yours into opening your address with that code. The user's organisation is then connected to the attacker's clinic, and the organisation's payments go to the attacker. If `state` is predictable, such as the organisation's address or id, the attacker can also choose which organisation it applies to. Rivr refuses a `state` of the wrong form, but only you can check that it belongs to this very session. PKCE stops a stray code from being redeemed. It doesn't replace `state`.

**Node**

```js
import { createHash, randomBytes } from "node:crypto";

// A new pair for every attempt. Store both on the server, with the organisation, the user and an expiry of 15 minutes.
const state = randomBytes(32).toString("base64url");
const codeVerifier = randomBytes(32).toString("base64url");
const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");
```

**Python**

```python
import base64
import hashlib
import secrets

# A new pair for every attempt. Store both on the server, with the organisation, the user and an expiry of 15 minutes.
state = secrets.token_urlsafe(32)
code_verifier = secrets.token_urlsafe(32)
digest = hashlib.sha256(code_verifier.encode()).digest()
code_challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode()
```

## 4. Send the browser to Rivr

*Journal system → Rivr*

Send the browser to `https://test.rivr.io/oauth/authorize` with six parameters, each of them once: `response_type=code`, `client_id`, `redirect_uri` (exactly the registered one), `state`, `code_challenge` and `code_challenge_method=S256`. It is the browser that goes there, not your server. The call takes no `scope`: the client's scopes apply, and the person sees what they give.

If something is wrong with `response_type`, `state` or PKCE the browser is sent back with `error=invalid_request`. If the client or the address is wrong, if `state` is missing or longer than 512 characters, or if a parameter appears twice, the browser stays on Rivr's error page (`400`, with `invalid_client`, `invalid_redirect_uri` or `invalid_request`). It is never sent to an address that hasn't been checked. If the attempt has already been started in another browser, the error page gives `started_in_another_browser`, and if the browser doesn't accept the cookie that ties the attempt to it, the page gives `cookies_required`.

> **Warning.** Never put clinic or personal data in the address. A sole proprietorship's organisation number is the owner's personal identity number, and the address ends up in logs. There is no pre-fill here: the pre-fill of a new clinic goes in the body of `POST /api/public/onboarding-links`.

**Node**

```js
const authorize = new URL("https://test.rivr.io/oauth/authorize");
authorize.search = new URLSearchParams({
  response_type: "code",
  client_id: process.env.RIVR_CLIENT_ID,
  redirect_uri: "https://journal.example.com/rivr/callback",
  state,
  code_challenge: codeChallenge,
  code_challenge_method: "S256",
});
// Send the browser there. Nothing about the clinic or the person goes in this address.
res.redirect(authorize.toString());
```

**Python**

```python
from urllib.parse import urlencode

query = urlencode({
    "response_type": "code",
    "client_id": os.environ["RIVR_CLIENT_ID"],
    "redirect_uri": "https://journal.example.com/rivr/callback",
    "state": state,
    "code_challenge": code_challenge,
    "code_challenge_method": "S256",
})
# Send the browser there. Nothing about the clinic or the person goes in this address.
return redirect(f"https://test.rivr.io/oauth/authorize?{query}")
```

## 5. The clinic signs in and approves

*Clinic → Rivr*

The person meets a page at Rivr with your name, signs in with BankID, picks the clinic and reads what the connection gives. Your system does nothing in this step and doesn't learn who signed in.

- **BankID every time.** The flow never skips the sign-in, and the approval is refused if the sign-in is older than ten minutes. The person is then sent to BankID again, and no error comes to you.
- **Who may approve:** staff at the clinic with a role that may give an external system access. Rivr admins and machine tokens may not. The clinic gets an email at its registered contact address on every connection and disconnection in this flow. A sandbox clinic gets none.
- **Which clinics can be picked:** a clinic with no platform, and a clinic already connected to you, in which case the approval is recorded and nothing else changes. A clinic on another platform can't be picked, and neither can a clinic with no platform that lacks Rivr Plus or isn't ready to take payments.
- **Swedish only so far.** The page, the approval's text and the email are in Swedish in this version, and the sign-in is Swedish BankID.
- **What the person approves:** what you may do, and that it also covers the clinic's older orders. They become visible to you and can be refunded.
- **The approval connects nothing.** The connection is made only when you redeem the code.

**Why:** The connection is made at redemption, when you have proven your secret and your verifier and checked that `state` belongs to the session. A code that is never redeemed gives no access.

## 6. The browser comes back: check state first

*Rivr → Journal system*

The browser comes back to your address with `?code=…&state=…` or `?error=…&state=…`. Check `state` first, in both cases. If it doesn't match the session, do nothing more and show an error. A `state` that has already been used is wrong too. Discard `state` and the verifier after an error on the address as well, and create a new pair next time.

The errors on the address:

- `access_denied`: the person cancelled. Nothing is connected.
- `account_required`: the person has said the clinic has no Rivr account. Rivr hasn't checked it. Start an onboarding. If the clinic is already a customer it is told so in Rivr's flow, and the onboarding never becomes `signed`: then the connection applies.
- `clinic_not_ready`: the clinic can't be connected yet. The person has seen the reason at Rivr. Try again once the clinic is ready.
- `invalid_request`: something in your call to `/oauth/authorize` is wrong.
- `temporarily_unavailable`: the connection isn't switched on for your platform, or Rivr is getting too many new attempts at the moment. Try again later.
- `server_error`: Rivr couldn't save the request. Start over with a new `state` and a new verifier.

**Why:** `state` is checked before the code exchange, so that no code is redeemed and no clinic is connected for the wrong session. After the exchange it is too late: the connection is already made.

**Node**

```js
// GET /rivr/callback. The state comes first, for an error as well as for a code.
const attempt = await attempts.takeOnce(req.query.state, req.session.id); // Gone after this read.
if (!attempt || attempt.expiresAt < Date.now()) {
  return res.status(400).send("The connection could not be verified.");
}
if (req.query.error) return showConnectionError(res, req.query.error);
return exchangeCode(req.query.code, attempt.codeVerifier, attempt.organisationId);
```

**In the portal:** [The error codes for the account connection](https://developers.rivr.io/errors#errors-connection)

## 7. Exchange the code for the clinic's id

*Journal system → Rivr*

`POST https://auth.test.rivr.io/oauth/token` from your server, with `grant_type=authorization_code`, `code`, `redirect_uri` (the same value as before), `code_verifier`, `client_id` and `client_secret`. **The body is form-encoded only**, unlike `/connect/token`, which takes JSON only. The client may instead authenticate with HTTP Basic, but not both ways. The code is valid for five minutes and once.

The answer carries `merchant_id`, which is what you store, and the clinic is now connected to your platform. `access_token` is an ordinary merchant token for one hour, with no refresh token. It is there because the OAuth standard and ready-made libraries require it. There is no publishable key.

The errors have the form `{"error":"…","error_description":"…"}`:

- `invalid_grant` (`400`): the code couldn't be redeemed. Start over from `/oauth/authorize`. A wrong address, a wrong verifier and an expired code burn it. The answer doesn't say which.
- `invalid_client` (`401`): a wrong `client_id` or `client_secret`. That doesn't burn the code.
- `invalid_request` (`400`): a parameter is missing, the body isn't form-encoded or the client authenticates in two ways.
- `unsupported_grant_type` (`400`): anything other than `authorization_code`.
- `server_error` (`500`): the clinic may already be connected. See the code's page.

The routes under `/oauth/*` have a limit per minute and IP address (`/rate-limits`). Over it the answer is `429` with no body and no `Retry-After`. Wait at most a minute and try again: nothing has been done, and the code isn't spent.

**What your system does:** Show `merchant_name` and `organisation_number` to the person and get a confirmation before you store `merchant_id` on the organisation. It is the one check that works however the code reached you. First check whether the id already belongs to an organisation with you, also through a search on the organisation number if you match that way today. If it does, store nothing and don't disconnect, since that would stop an integration in use. If the id was unknown to you before the attempt and the person says no, disconnect with `POST /oauth/deauthorize`. A 409 there means the clinic was already on your platform: nothing changed, and nothing more is needed. If an earlier attempt for this clinic ended in `server_error`, contact Rivr instead. Don't store `access_token`: fetch a merchant token when you need one, as in the guide Connect as a platform. `organisation_number` can be compared with the organisation's own, but the key is `merchant_id`, since a company can have several clinics with the same number. Treat the number as personal data in your logs. If the exchange fails the user must see an error, never a successful connection.

**macOS and Linux**

```bash
curl https://auth.test.rivr.io/oauth/token \
  -d grant_type=authorization_code \
  --data-urlencode code="$CODE" \
  --data-urlencode redirect_uri="https://journal.example.com/rivr/callback" \
  --data-urlencode code_verifier="$CODE_VERIFIER" \
  --data-urlencode client_id="$RIVR_CLIENT_ID" \
  --data-urlencode client_secret="$RIVR_CLIENT_SECRET"
```

**Node**

```js
// Form-encoded: a URLSearchParams body sets the content type.
const res = await fetch("https://auth.test.rivr.io/oauth/token", {
  method: "POST",
  body: new URLSearchParams({
    grant_type: "authorization_code",
    code,
    redirect_uri: "https://journal.example.com/rivr/callback",
    code_verifier: codeVerifier,
    client_id: process.env.RIVR_CLIENT_ID,
    client_secret: process.env.RIVR_CLIENT_SECRET,
  }),
});
// Over the limit: no body, nothing done and the code isn't spent. Wait at most a minute.
if (res.status === 429) throw new Error("Rivr answered 429: wait at most a minute and try again");
const answer = await res.json();
if (!res.ok) throw new Error(`Rivr refused the code: ${answer.error}`);
const merchantId = answer.merchant_id;
```

**Python**

```python
res = requests.post(
    "https://auth.test.rivr.io/oauth/token",
    data={  # Form-encoded, not JSON.
        "grant_type": "authorization_code",
        "code": code,
        "redirect_uri": "https://journal.example.com/rivr/callback",
        "code_verifier": code_verifier,
        "client_id": os.environ["RIVR_CLIENT_ID"],
        "client_secret": os.environ["RIVR_CLIENT_SECRET"],
    },
)
# Over the limit: no body, nothing done and the code isn't spent. Wait at most a minute.
if res.status_code == 429:
    raise RuntimeError("Rivr answered 429: wait at most a minute and try again")
answer = res.json()
if not res.ok:
    raise RuntimeError(f"Rivr refused the code: {answer['error']}")
merchant_id = answer["merchant_id"]
```

**Response (excerpt)**

```json
{
  "access_token": "eyJ…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "public:merchants:read public:orders:read public:orders:write …",
  "merchant_id": "aa2225b7-de35-4afa-bd9b-8a47577ce844",
  "merchant_name": "Exempelkliniken AB",
  "organisation_number": "5566778899"
}
```

## 8. Call Rivr as before, and show the account

*Journal system → Rivr*

After the connection everything is as in the guide Connect as a platform: `grant_type: merchant_token` on `/connect/token` with `merchant_id`, and the clinic is in `GET /api/public/merchants`.

Show the account with `GET /api/public/merchants/{merchantId}`. The answer carries `id`, `name`, `organisationNumber` and `externalId`. The only new member is `email`, the clinic's registered contact address, which exists on this route only and is left out when the clinic has none.

This answer has no flags for whether the clinic can take payments. `chargesEnabled` and `payoutsEnabled` exist only on an onboarding link.

**Request:** `GET /api/public/merchants/MERCHANT_ID` · Platform token · [Reference](https://developers.rivr.io/reference/api-public/getMerchant.md)

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

**Response (excerpt)**

```json
{
  "merchant": {
    "id": "aa2225b7-de35-4afa-bd9b-8a47577ce844",
    "name": "Exempelkliniken AB",
    "organisationNumber": "5566778899",
    "externalId": null,
    "email": "reception@example.com"
  }
}
```

## 9. Disconnect, and recognise a disconnected clinic

*Journal system → Rivr*

`POST https://auth.test.rivr.io/oauth/deauthorize`, form-encoded, with `client_id`, `client_secret` and `merchant_id`. The answer is `200` with `{"merchant_id":"…"}`, also when you repeat the call. `404` with `merchant_not_connected` means the clinic isn't connected to your platform and Rivr has no record of it having been. A clinic connected here can always be disconnected, also when the connection isn't switched on. A clinic that came to your platform in another way, for example from the funnel, also through your own onboarding link, answers `409` with `clinic_connect_no_consent` and isn't changed, also if it has approved here afterwards: contact Rivr to end it. The clinic can also be disconnected from Rivr's side, and then no webhook comes to you: there is no event for a disconnection.

This is what happens when the connection ends:

- new merchant tokens are refused at once with `401` and `invalid_client`;
- the clinic disappears from the list, and `GET /api/public/merchants/{merchantId}` answers `404`;
- tokens already issued live for at most one more hour;
- orders under way can send webhooks to your address for up to seven days, without a signature from your platform's keys. Refuse a delivery without a valid signature;
- payments already under way are not affected, but new orders can't be created without a token.

> **Warning.** `invalid_client` on `merchant_token` doesn't on its own mean the clinic is disconnected. The same code is given for a wrong secret and for an inactive client, so after a bad rotation every clinic looks disconnected. Confirm it: fetch a platform token and read `GET /api/public/merchants/{merchantId}`. `404` with `merchant_not_found` means disconnected. If the platform token can't be fetched it is your keys that are wrong, not the connection.

**macOS and Linux**

```bash
curl https://auth.test.rivr.io/oauth/deauthorize \
  --data-urlencode client_id="$RIVR_CLIENT_ID" \
  --data-urlencode client_secret="$RIVR_CLIENT_SECRET" \
  --data-urlencode merchant_id="$MERCHANT_ID"

# → { "merchant_id": "…" }
```

**Node**

```js
const res = await fetch("https://auth.test.rivr.io/oauth/deauthorize", {
  method: "POST",
  body: new URLSearchParams({
    client_id: process.env.RIVR_CLIENT_ID,
    client_secret: process.env.RIVR_CLIENT_SECRET,
    merchant_id: merchantId,
  }),
});
// 200 also when you repeat it. 404 merchant_not_connected: not connected to your platform, and Rivr has no record of it having been.
// 409 clinic_connect_no_consent: the account connection didn't connect this clinic. Nothing changed: contact Rivr.
if (!res.ok && res.status !== 404 && res.status !== 409) throw new Error(`Rivr answered ${res.status}`);
```

## 10. Test without a real BankID sign-in

*Journal system → Rivr*

The staff at sandbox clinics are test identities who can't sign in with BankID, so the sandbox has a simulated approval: `POST https://test.rivr.io/api/clinic-connect/sandbox/approve`, server to server, with the client's secret. It exists in the sandbox only and answers `404` in production. There is no test panel on Rivr's page.

The code example takes `request` from `Location` in the answer from `/oauth/authorize`. If `request` is missing, Rivr refused the start: `error` on the address says why, for example `temporarily_unavailable` while the connection isn't switched on, and with no address it is Rivr's error page. An approval without `request` only gets `404`.

Rivr creates the new sandbox clinics for your organisation. They have no platform, so the connection is a real change from no platform to you. Everything after the approval is the real flow: the same code, the same exchange and the same disconnection.

The errors have the form `{"code":"…"}`: a clinic not marked as a sandbox clinic gets `403` with `clinic_connect_not_a_sandbox_clinic`, a request that is gone `404` with `clinic_connect_request_not_found`, a used one `409` with `clinic_connect_request_used`, and a clinic on another platform `409` with `clinic_connect_other_platform`.

To try the errors on your address, call `POST https://test.rivr.io/api/clinic-connect/requests/{request}/cancel` with `{"reason":"access_denied"}`, `account_required` or `clinic_not_ready`, and follow `redirectUrl` in the answer. The call needs the cookie from `Set-Cookie` in the answer from `/oauth/authorize`, otherwise the answer is `404`. A real BankID sign-in also works in the sandbox, if Rivr adds your developer as staff on a sandbox clinic. A test clinic that isn't a sandbox clinic can only be picked once it is ready to take payments.

**What your system does:** Prove at least this, with the platform client: a wrong `state` is refused with no code exchange, a `state` that has already come back with an error is refused with no code exchange, a wrong `code_verifier` gives `invalid_grant`, a cancelled flow gives `access_denied`, `merchant_token` is refused before the connection, works after it and is refused again after the disconnection. Don't try it with your organisation's own keys. They reach the sandbox clinics regardless of the connection, so they prove nothing about it.

**Node**

```js
// 1. Start as a browser would, but read the answer instead of following it.
const authorize = new URL("https://test.rivr.io/oauth/authorize");
authorize.search = new URLSearchParams({
  response_type: "code",
  client_id: process.env.RIVR_CLIENT_ID,
  redirect_uri: "https://journal.example.com/rivr/callback",
  state,
  code_challenge: codeChallenge,
  code_challenge_method: "S256",
});
const started = await fetch(authorize, { redirect: "manual" });
const landing = new URL(started.headers.get("location") ?? "", "https://test.rivr.io");
const request = landing.searchParams.get("request");
// No request: Rivr refused the start. error on the address says why. With no address it is Rivr's error page, or a 429.
if (!request) throw new Error(`Rivr refused the start: ${started.status} ${landing.searchParams.get("error") ?? ""}`);

// 2. Approve for a sandbox clinic, server to server, with the client's secret. JSON, this one.
const res = await fetch("https://test.rivr.io/api/clinic-connect/sandbox/approve", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    request,
    merchantId: process.env.RIVR_SANDBOX_MERCHANT_ID,
    clientId: process.env.RIVR_CLIENT_ID,
    clientSecret: process.env.RIVR_CLIENT_SECRET,
  }),
});
if (res.status === 429) throw new Error("Rivr answered 429: wait at most a minute and try again");
const answer = await res.json();
if (!res.ok) throw new Error(`Rivr answered ${answer.code}`);

// 3. Open answer.redirectUrl in a browser with your own session: your real handler takes the code and the state from there.
const redirectUrl = answer.redirectUrl;
```

## Common pitfalls

- Using the organisation's address or id as `state`. It is predictable and opens for OAuth CSRF, and Rivr refuses an address. Draw a new value for every attempt and bind it to the session.
- Mixing up the token requests. `/oauth/token` takes form-encoded only. `/connect/token` takes JSON only.
- Showing "connect again" as soon as `merchant_token` answers `invalid_client`. Confirm with a platform token and `GET /api/public/merchants/{merchantId}` first, or every clinic looks disconnected after a bad rotation.
- Sending the user to a success page although the code exchange failed. Store nothing and show no success until the answer is `200`.
- Repeating the code exchange with the same code. A used code gives `invalid_grant` and shows up as an error at Rivr. The connection isn't touched, but start over from `/oauth/authorize` to get a new answer.
- Waiting for `chargesEnabled` on `GET /api/public/merchants/{merchantId}`. The field isn't there. A clinic with no platform can be connected only once it has taken payments or is ready to. A clinic already connected to you isn't checked, since nothing changes, and neither is a sandbox clinic. A successful connection therefore doesn't by itself say that the clinic can take payments.

## 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 from your system](https://developers.rivr.io/guides/onboarding-links.md): 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.
- [Test in the sandbox](https://developers.rivr.io/guides/testing.md): What you can try in the sandbox today, how to do it, which test values exist and what does not work yet.
- [Errors and retries](https://developers.rivr.io/guides/errors-retries.md): One way to handle errors against /api/public: read the code, decide whether to send the call again, wait the right time and send the same Idempotency-Key again.
