# Call /api/public with a key

> An organisation key for a clinic's orders, settlements, subscriptions and devices: which clinic the call is about, which routes the key reaches and what the refusals mean.

You have an organisation key and want to create, read or refund a clinic's orders in `/api/public`, or read its settlements and devices, without a platform client. The key works there too, but more than the address differs from the platform client: which clinic the call is about, which routes are open and how a missing capability shows.

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

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. The key's token

*Journal system → Rivr*

The key's token comes from rivr-auth, form-encoded, with `resource=https://api.rivr.io`. It is fetched from `https://auth.test.rivr.tech/connect/token`, with `CLIENT_ID` replaced by the key's `client_id` and the secret in `RIVR_CLIENT_SECRET`. If you also have a platform client in the same environment, don't put the key's secret in the platform's variable: change the name in the example, for example to `RIVR_KEY_CLIENT_SECRET`. A token lasts about an hour: cache it.

In front of `/api/public` Rivr recognises such a token, checks it, picks the clinic and swaps it for a short clinic token that carries only what the key may do there. That token is what reaches `/api/public`: the key's own token and `Rivr-Organization` never do. A platform token from `auth.test.rivr.io`, a merchant token and a call without a token go through exactly as before.

**What your system does:** Fetch the key's token from `auth.test.rivr.tech`, not from the platform client's address, and send it as `Authorization: Bearer` on every call. The token and the secret stay on your server.

**macOS and Linux**

```bash
curl -X POST https://auth.test.rivr.tech/connect/token \
  -d grant_type=client_credentials \
  -d client_id=CLIENT_ID \
  --data-urlencode client_secret="$RIVR_CLIENT_SECRET" \
  -d resource=https://api.rivr.io
```

**Node**

```js
const TOKEN_ENDPOINT = "https://auth.test.rivr.tech/connect/token";

// Cache the token — it lasts about an hour.
async function getAccessToken() {
  const res = await fetch(TOKEN_ENDPOINT, {
    method: "POST",
    body: new URLSearchParams({
      grant_type: "client_credentials",
      client_id: "CLIENT_ID",
      client_secret: process.env.RIVR_CLIENT_SECRET,
      resource: "https://api.rivr.io",
    }),
  });
  const { access_token } = await res.json();
  return access_token;
}

const accessToken = await getAccessToken();
```

**Python**

```python
import os, requests

TOKEN_ENDPOINT = "https://auth.test.rivr.tech/connect/token"

def get_access_token():                       # cache ~1h
    r = requests.post(TOKEN_ENDPOINT, data={  # data= is form-encoded
        "grant_type": "client_credentials",
        "client_id": "CLIENT_ID",
        "client_secret": os.environ["RIVR_CLIENT_SECRET"],
        "resource": "https://api.rivr.io",
    })
    r.raise_for_status()
    return r.json()["access_token"]

access_token = get_access_token()
```

**Response (excerpt)**

```json
{ "access_token": "eyJ…", "token_type": "Bearer", "expires_in": 3600 }
```

## 2. Which clinic: Rivr-Organization

*Journal system → Rivr*

Every call under `/api/public` is about exactly one clinic. Send `Rivr-Organization` with the clinic's id: one GUID, which for a clinic is the same id as its `merchantId`.

Leave the header out only when the key reaches exactly one clinic, for example a key made by the clinic itself or by a partner with a single clinic: that clinic is used. A key that reaches several clinics must always send it, or the answer is `400` with `organization_required`.

A clinic the key doesn't reach, a clinic that isn't active, and a key whose capabilities give nothing on `/api/public` (`events:read` on its own gives nothing) all get `403` with `organization_not_accessible`.

**Why:** With a platform client the clinic lives in the token, one merchant token per clinic. A key has one token for everything it reaches, so the clinic is chosen per call.

## 3. Read an order's status

*Journal system → Rivr*

`GET /api/public/orders/{orderId}/status` is the call `orders:read` is enough for: the order's id and status, without personal data. Send `Rivr-Organization` with the clinic's id (`MERCHANT_ID` in the example). The status is a number in `/api/public`: `1` is `Created`, `2` `Pending`, `4` `Completed`, `8` `Refunded` and `32` `Cancelled`. If the order doesn't exist, `/api/public` answers `204` with no body.

Creating, refunding and cancelling orders works the same way (`PUT /api/public/orders/{orderId}`, `POST …/refund` and `POST …/cancel`), with the same bodies as when you call as a platform. They need `orders:manage` and `customers:read` and, for a refund and a cancel, a new `Idempotency-Key` on every call. The guides Payment on the phone and Cancel and refund show them.

**What your system does:** Read the order's status with the key, and act on the webhook, not on your own call. Tick `customers:read` only if the key should read or create the whole order: to confirm a payment, `orders:read` is enough.

**Request:** `GET /api/public/orders/7b1e7c3a-2f4d-4a51-9c7e-5d0a6b8e2c14/status` · The key's token · [Reference](https://developers.rivr.io/reference/api-public/getOrderStatus.md)

```bash
curl -X GET https://api.test.rivr.io/api/public/orders/7b1e7c3a-2f4d-4a51-9c7e-5d0a6b8e2c14/status \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Rivr-Organization: MERCHANT_ID"
```

**Response (excerpt)**

```json
{ "id": "7b1e7c3a-2f4d-4a51-9c7e-5d0a6b8e2c14", "status": 1 }
```

**In the portal:** [The guide Payment on the phone](https://developers.rivr.io/guides/pay-by-link)

## 4. What the key can call

*Rivr*

These routes work with a key, and each needs the capabilities named before it, on the clinic:

**Any capability except `events:read` on its own:** `GET /api/public/health-secure`.

**`settings:read`:** `GET /api/public/devices`, `GET /api/public/merchants/{merchantId}`.

**`orders:manage` and `customers:read` together:** `PUT /api/public/orders/{orderId}`, `POST /api/public/orders/{orderId}/refund`, `POST /api/public/orders/{orderId}/cancel`, `POST /api/public/orders/{orderId}/send-to-device`, `POST /api/public/subscriptions`.

**`orders:read` and `customers:read` together:** `GET /api/public/orders/{orderId}`, `GET /api/public/orders/{orderId}/receipts/{receiptId}`, `GET /api/public/subscriptions/{subscriptionId}`.

**`orders:read`:** `GET /api/public/orders/{orderId}/status`.

**`economy:read`:** `GET /api/public/order-settlements`, `GET /api/public/order-settlements/{orderSettlementId}`.

Reading or creating orders also needs `customers:read`, because the order carries the patient's details. `orders:read` on its own gives the status only: id and status, no personal data.

`GET /api/public/merchants/{merchantId}` answers only for the clinic's own id, the one the call is about. Any other id gets `404` with `merchant_not_found`, the same as a clinic that doesn't exist.

Everything else in the reference is closed to a key and answers `403` with `route_not_available` without reaching the monolith: the list of clinics (`GET /api/public/merchants`), `GET /api/public/order-settlements/next-unread`, the onboarding routes and every other route in the reference that isn't listed here, also one added later. The exception is the health routes (`/api/public/health`, `/api/public/health/ready` and `/api/public/health/client-ip`): they need no token and work as before.

**What your system does:** Tick the least that is enough on the API keys page. A change applies within about a minute, with the same key. If your integration needs a route that is closed to keys, for example the list of clinics or onboarding, it is the platform client you need: the guide Which key do I need? says when.

**In the portal:** [The guide Which key do I need?](https://developers.rivr.io/guides/which-key)

## 5. Refusals from the key check

*Rivr → Journal system*

Before a call reaches /api/public, Rivr's key check can refuse it. The answer is `application/problem+json` with a stable `code`, and each code has a page of its own. Program against `code`, never against the text.

`key_invalid` (`401`): The key's token is not valid.

`organization_required` (`400`): Organisation missing.

`organization_invalid` (`400`): Invalid organisation.

`organization_not_accessible` (`403`): Organisation not accessible.

`route_not_available` (`403`): Route closed to keys.

`access_check_unavailable` (`503`): Permissions could not be checked.

A route in the list that the key lacks a capability for answers `403` with `invalid_scope` from /api/public itself, not from the key check, and names the scope that is missing. Give the key the capability on the API keys page. What /api/public itself answers keeps the form it has always had.

**In the portal:** [All error codes](https://developers.rivr.io/errors)

## 6. Platform token or key

*Rivr*

**Where the token comes from.** A platform token: `POST https://auth.test.rivr.io/connect/token` with a JSON body. The key's token: `POST https://auth.test.rivr.tech/connect/token`, form-encoded, with `resource=https://api.rivr.io`.

**How the clinic is chosen.** A platform gets a merchant token per clinic (`grant_type: merchant_token`), and the clinic lives in the token. A key has one token for everything it reaches, and the clinic is the `Rivr-Organization` header on each call.

**What it can call.** A platform token: the routes its client's scopes allow, the list of clinics and onboarding included. A key: only the routes above, with the capabilities chosen for it.

## Common pitfalls

- Leaving out `Rivr-Organization` when the key reaches several clinics. The answer is `400` with `organization_required`. The header can be left out only when the key reaches exactly one clinic.
- Sending `Rivr-Organization` with a clinic the key doesn't reach, or using a key that has only `events:read`. Both get `403` with `organization_not_accessible`.
- Ticking `orders:manage` without `customers:read` and expecting to create orders. The key gets only `public:health:read`, and the write call answers `403` with `invalid_scope`.
- Calling the list of clinics (`GET /api/public/merchants`) or onboarding with a key. Those routes are closed to keys and answer `403` with `route_not_available`: they are the platform client's routes.
- Looking for an error code of its own for a missing capability. On `/api/public` a missing capability shows as `invalid_scope`.

## Next guide

- [Payment on the phone](https://developers.rivr.io/guides/pay-by-link.md): The patient gets a payment link by SMS and pays whenever it suits them.
- [Cancel and refund](https://developers.rivr.io/guides/cancel-refund.md): Cancel an unpaid order, pay back a paid one — and wait for the webhook.
- [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.
