# Pharmacy Orders
Source: https://docs.rxscale.com/api-reference/external-pharmacy/orders
List, view, and update pharmacy order status
# Pharmacy Orders
Manage pharmacy orders — view incoming orders (prescription-based and over-the-counter) and update their status as you process them.
## List Orders
Page number (0-indexed)
Number of items per page (max 200)
Filter by status (e.g., `waiting for pharmacy`, `on-hold`, `in-progress`, `completed`)
Search term. Case-insensitive substring match against shop order name (e.g. `#1234`), pharmacy order name, patient name, order UID, and pharmacy order UID.
Matching on the **shop order name** is only active for pharmacies that have the shop order name enabled (see the [breaking change note](#shop-order-name-visibility) below). When it is disabled, searching by the shop order name returns no results; the other search fields are unaffected.
Inclusive lower bound on the order's `created_at` Unix timestamp (seconds). Orders created before this moment are excluded.
Inclusive upper bound on the order's `created_at` Unix timestamp (seconds). Orders created after this moment are excluded.
Required for group-wide API keys
```bash theme={null}
GET /v1/external_pharmacy_api/pharmacy_orders/
```
Both date bounds are optional and may be used independently — send only `start_date` for "everything since", or only `end_date` for "everything up to". Both are inclusive, so an order created at exactly `start_date` or exactly `end_date` is included.
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/external_pharmacy_api/pharmacy_orders/?start_date=1735689600&end_date=1738368000&limit=100" \
-H "X-API-Key: your-api-key"
```
### Response
```json theme={null}
{
"data": [
{
"uid": "po-abc123",
"status": "waiting for pharmacy",
"name": "#1001",
"shop_order_name": "#1234",
"external_status": "OPEN",
"pharmacy": {
"uid": "ph-xyz",
"display_name": "City Pharmacy"
},
"order": {
"uid": "ord-123",
"delivery_address": {
"first_name": "Max",
"last_name": "Mustermann",
"street": "Hauptstr.",
"house_number": "1",
"zip_code": "10115",
"city": "Berlin",
"country": "Germany",
"additional_address": null,
"province": null
},
"invoice_address": {
"first_name": "Max",
"last_name": "Mustermann",
"street": "Hauptstr.",
"house_number": "1",
"zip_code": "10115",
"city": "Berlin",
"country": "Germany",
"additional_address": null,
"province": null
},
"priority": 5
},
"order_items": [
{
"uid": "oi-789",
"amount": 1,
"sku": {
"uid": "sku-456",
"display_name": "Medication X 100mg",
"pzn": "12345678"
},
"total_paid_amount": 1299
}
],
"shop_shipping_methods": [
{
"uid": "shop-shipping-method-uid",
"display_name": "DHL Standard",
"external_id": "shopify-standard",
"pharmacy_mapping": {
"pharmacy_uid": "pharmacy-uid",
"shipping_method_identifier_for_pharmacy": "DHL_STANDARD"
}
}
],
"shipping_costs_amount": 499,
"shipping_costs_currency": "EUR"
}
],
"totalRegistries": 42,
"totalPages": 1
}
```
### Order Response Fields
**The documented fields are the core contract, not an exhaustive list.** Pharmacy order list and detail responses are serialised directly from the underlying order model, so they can include additional fields beyond the ones documented here, and new fields can be added at any time without notice.
Build your integration to **ignore unknown fields** rather than rejecting the response. Configure your JSON deserialiser to skip properties it does not recognise (for example `@JsonIgnoreProperties(ignoreUnknown = true)` in Jackson, or a non-strict schema in Pydantic/marshmallow). Only the fields documented on this page are covered by our compatibility guarantees — treat anything else as informational and do not depend on it.
Human-readable shop order name from the originating shop (e.g. `#1234`). `null` for orders without a linked shop order, and `null` whenever the shop order name is **disabled** for the owning pharmacy (see the breaking change note below).
**Breaking change — `shop_order_name` is now off by default.**
`shop_order_name` is now controlled by a per-pharmacy setting that is **disabled by default**. While it is disabled, every order belonging to that pharmacy returns `shop_order_name: null` (in both the list and the order-detail responses), and the order can no longer be found by searching for its shop order name.
Previously this field was always populated whenever a linked shop order existed. If your integration relies on `shop_order_name` — for display, reconciliation, or `search` — the shop can enable the shop order name itself for the affected pharmacy in the admin tool (the pharmacy's detail page, under the **Shop Order Name** setting); no RxScale involvement is required. Once enabled, the field is populated and searchable again exactly as before.
Do not treat a `null` `shop_order_name` as "no shop order" — it can also mean the setting is disabled. Use the `order.uid` / pharmacy order `uid` as your stable identifiers.
Shop shipping methods attached to the connected shop order. Each entry includes
the shop method (`uid`, `display_name`, `external_id`) and the pharmacy-specific
mapping when one has been configured.
Pharmacy-specific mapping for this shop shipping method. When present, it includes
`pharmacy_uid` and `shipping_method_identifier_for_pharmacy`.
Shipping costs in cents, for example `499` for EUR 4.99. This value is separate
from product line item prices.
ISO currency code for the shipping costs, for example `EUR`.
Priority hint for handling order sooner. Higher = more urgent. `0` means no special priority.
Shop shipping methods come from connected shop orders. Pharmacists can configure
pharmacy-specific identifiers for each shop shipping method in the pharmacy tool
settings. If no mapping exists yet, the API still returns the shop shipping method
with `pharmacy_mapping: null`. After a pharmacist saves a mapping, future order
responses include the configured `shipping_method_identifier_for_pharmacy`.
## Get Order Details
```bash theme={null}
GET /v1/external_pharmacy_api/pharmacy_orders/{pharmacy_order_uid}
```
Returns the full order including patient data, doctor data, and prescription file (if available).
`doctor_data` and `prescription_file` are `null` for orders that contain only over-the-counter (OTC) products — these orders have no prescription attached.
`shop_order_name` follows the same per-pharmacy setting here as in the list response — it is `null` while the setting is disabled (the default). See the [breaking change note](#shop-order-name-visibility) above.
### Response (additional fields)
```json theme={null}
{
"uid": "po-abc123",
"shop_order_name": null,
"patient_data": {
"uid": "pat-123",
"display_name": "Max Mustermann",
"email": "max@example.com",
"date_of_birth": "1990-01-15"
},
"doctor_data": {
"uid": "doc-456",
"display_name": "Dr. Schmidt"
},
"prescription_file": {
"filename": "prescription_001.pdf",
"content_base64": "JVBERi0xLjQK..."
},
"shop_shipping_methods": [
{
"uid": "shop-shipping-method-uid",
"display_name": "DHL Standard",
"external_id": "shopify-standard",
"pharmacy_mapping": null
}
],
"shipping_costs_amount": 499,
"shipping_costs_currency": "EUR",
"prepaid": 1,
"payouts": [
{
"status": "projected",
"amount": 1299,
"currency": "EUR",
"component_type": "item_rest",
"routing_description": "Medication X 100mg rest amount",
"provider_route_id": null,
"pharmacy_order_uid": "po-abc123",
"pharmacy_order_name": "#1001",
"pharmacy_order_created_at": 1711899000,
"routing_created_at": null
}
]
}
```
### Line item pricing
Each `order_items[]` entry carries two prices:
* `pharmacy_sku.price` — the pharmacy's list price for the SKU, in cents, as an **order-time snapshot** (the price captured when the order was placed). It does not change if the pharmacy later updates its list price.
* `total_paid_amount` — the amount the patient actually paid for the line, in cents (gross). It is populated for **prepaid** orders (those with a signed physical prescription, i.e. `prepaid: 1`) and is `0` when no payment applies to the line.
For prepaid orders, reconcile against `total_paid_amount` rather than `pharmacy_sku.price`.
### Payouts
Order detail responses include top-level `payouts`. Each entry uses the same shape as the [Payouts](/api-reference/external-pharmacy/payouts) endpoint.
Payout components for this pharmacy order where the requested pharmacy is the receiver. Paid physical-prescription orders return `projected` payout previews based on current order values and routing configuration. Completed orders return `routed` payouts once a persisted split-payment route exists.
`projected` for an indicative preview, or `routed` for a persisted split-payment route created after pharmacy order completion.
Payout amount in cents.
ISO 4217 currency code, for example `EUR`.
The payout component, such as `item_rest`, `item_markup`, or `shipping`.
Human-readable description of the routed or projected component.
Payment provider route identifier. This is populated for routed payouts when the provider returned an identifier, and `null` for projected payouts.
UID of the related pharmacy order.
Human-readable pharmacy order name, for example `#1001`.
Unix timestamp when the pharmacy order was created.
Unix timestamp when the split-payment route was created. This is `null` for `projected` payouts and populated for `routed` payouts.
Actual routing only happens when the pharmacy order is completed. `projected` payouts are indicative, only shown for paid physical-prescription orders, and can change before completion. On completion the order leaves the `projected` set; it appears as `routed` only if a split-payment route was created. There is no persistent projected history after completion.
## Update Order Status
```bash theme={null}
PATCH /v1/external_pharmacy_api/pharmacy_orders/{pharmacy_order_uid}/status
```
### Request Body
```json theme={null}
{
"status": "in-progress"
}
```
New order status. See the table below for accepted values.
Free-text explanation for the status change. **Required (and must be non-blank) when transitioning to `on-hold` from any other status** — the comment becomes the description of the admin issue thread that is automatically opened for the on-hold. Ignored for all other status transitions.
### Allowed Status Values
| Status | Description |
| ---------------------- | ------------------------------------------------------------------ |
| `waiting for pharmacy` | Order is in your queue, waiting to be processed |
| `pending review` | Order is being reviewed |
| `on-hold` | Order is paused while the pharmacy and admin team clarify an issue |
| `in-progress` | Order is being prepared |
| `ready_for_pickup` | Order is packed and ready for pickup or shipping |
| `cancelled` | Order was cancelled |
Do not set `completed` through this endpoint. Use the dedicated complete order endpoint below so RxScale can finalize the order, reduce stock, and publish the related events.
### Putting an Order On Hold
When you move an order into `on-hold`, you must include a `comment` describing why the order is being paused. RxScale opens an admin issue thread automatically and uses your comment as the thread description so the admin team has the context they need to follow up.
```bash theme={null}
curl -X PATCH "https://api.rxscale.com/v1/external_pharmacy_api/pharmacy_orders/po-abc123/status" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"status": "on-hold",
"comment": "Out of stock until 2026-06-01"
}'
```
A request without a `comment`, or with a comment that is only whitespace, returns a `400` response with body:
```json theme={null}
{
"error": {
"comment": ["A comment is required when setting an order on hold"]
}
}
```
Subsequent `on-hold` PATCH requests for an order that is already `on-hold` do not require a new comment — they are treated as idempotent re-sends.
## Complete Order
```bash theme={null}
PATCH /v1/external_pharmacy_api/pharmacy_orders/{pharmacy_order_uid}/complete_order
```
Completes the pharmacy order, reduces stock for the pharmacy SKUs on the order, and publishes order update notifications. Requires the `orders_write` permission.
Required for group-wide API keys
### Request Body
```json theme={null}
{
"tracking_links": [
{
"tracking_link": "https://tracking.example.com/parcel/123",
"carrier": "DHL"
}
]
}
```
`tracking_links` is optional. If provided, the first tracking link is forwarded with the shipment update.
Allowed `carrier` values are `DHL`, `DPD`, `UPS`, `Hermes`, `FedEx`, and `Other`.
```bash theme={null}
curl -X PATCH "https://api.rxscale.com/v1/external_pharmacy_api/pharmacy_orders/po-abc123/complete_order" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"tracking_links": [
{
"tracking_link": "https://tracking.example.com/parcel/123",
"carrier": "DHL"
}
]
}'
```
### Response
```json theme={null}
{
"uid": "po-abc123",
"status": "completed",
"name": "#1001",
"external_status": "OPEN",
"pharmacy": {
"uid": "pharmacy-uid",
"display_name": "Example Pharmacy"
},
"order_items": [
{
"uid": "oi-789",
"amount": 1,
"sku": {
"uid": "sku-456",
"display_name": "Medication X 100mg",
"pzn": "12345678"
}
}
],
"shop_shipping_methods": [
{
"uid": "shop-shipping-method-uid",
"display_name": "DHL Standard",
"external_id": "shopify-standard",
"pharmacy_mapping": {
"pharmacy_uid": "pharmacy-uid",
"shipping_method_identifier_for_pharmacy": "DHL_STANDARD"
}
}
],
"shipping_costs_amount": 499,
"shipping_costs_currency": "EUR"
}
```
### Validation Error Response
When `tracking_links` contains an unsupported carrier or an invalid tracking link, the API returns `400` with the validation error and the pharmacy summary so you can map the error back to the affected pharmacy. If the order is already completed, calling `complete_order` again remains idempotent only when no tracking data is sent; tracking links on an already-completed order are rejected with `400` so shipment details are not silently dropped.
```json theme={null}
{
"error": {
"tracking_links": {
"0": {
"tracking_link": ["Invalid tracking link"]
}
}
},
"pharmacy": {
"uid": "pharmacy-uid",
"display_name": "Example Pharmacy"
}
}
```
# Overview
Source: https://docs.rxscale.com/api-reference/external-pharmacy/overview
External Pharmacy API for pharmacy integrations
# External Pharmacy API
The External Pharmacy API enables pharmacies to manage their orders, update stock levels, and receive real-time notifications about order changes.
## Base Path
```
/v1/external_pharmacy_api
```
## Interactive API Documentation (Swagger)
A live Swagger UI is available for exploring and testing endpoints directly in your browser:
```
https://api.rxscale.com/v1/external_pharmacy_api/apidocs
```
The Swagger UI lets you try out API calls interactively. Authenticate with your API key to test against real data.
## Authentication
All endpoints require an API key via the `X-API-Key` header. See [Authentication](/authentication) for details.
### API Key Scoping
API keys can be scoped to either a single pharmacy or an entire pharmacy group:
* **Single-pharmacy keys** are tied to one specific pharmacy. All requests are automatically scoped to that pharmacy, and no additional parameters are needed.
* **Group-wide keys** cover all pharmacies within a pharmacy group. When using a group-wide API key, the `pharmacy_uid` query parameter is **required** on most endpoints to specify which pharmacy you are operating on.
```bash theme={null}
# Group-wide API key: pharmacy_uid is required
GET /v1/external_pharmacy_api/pharmacy_orders/?pharmacy_uid=your-pharmacy-uid
# Single-pharmacy API key: no pharmacy_uid needed
GET /v1/external_pharmacy_api/pharmacy_orders/
```
If you use a group-wide API key and omit the `pharmacy_uid` parameter on an endpoint that requires it, the request will return an error.
## Available Endpoints
| Method | Endpoint | Description |
| -------- | --------------------------------------- | --------------------------------------------------- |
| `GET` | `/pharmacy_orders/` | List pharmacy orders |
| `GET` | `/pharmacy_orders/{uid}` | Get order details |
| `PATCH` | `/pharmacy_orders/{uid}/status` | Update order status |
| `PATCH` | `/pharmacy_orders/{uid}/complete_order` | Complete an order |
| `GET` | `/payouts` | List projected and routed pharmacy payouts |
| `GET` | `/pharmacy_products/` | List products the pharmacy carries, with their SKUs |
| `GET` | `/pharmacy_skus/` | List pharmacy SKUs |
| `PATCH` | `/pharmacy_skus/{uid}` | Update SKU (price, stock, external\_id) |
| `PATCH` | `/pharmacy_skus/{uid}/stock` | Update stock level |
| `PATCH` | `/pharmacy_skus/{uid}/external_id` | Update external ID |
| `GET` | `/webhooks/` | List webhook subscriptions |
| `POST` | `/webhooks/` | Register a webhook |
| `DELETE` | `/webhooks/{uid}` | Remove a webhook |
## Required Permissions
| Endpoint | Required Permission |
| ------------------------- | ------------------------------------- |
| List/View orders | `orders_read` |
| List payouts | `orders_read` |
| Update or complete orders | `orders_write` |
| List products | `stock_read` |
| List SKUs | `stock_read` |
| Update SKU data | `stock_write` or `pharmacy_sku_write` |
| Manage webhooks | `webhooks_read` / `webhooks_write` |
# Payouts
Source: https://docs.rxscale.com/api-reference/external-pharmacy/payouts
View projected and routed pharmacy payout amounts
# Payouts
Use the payouts endpoint to review the amounts that are expected to be routed to your pharmacy for paid physical-prescription orders, and the amounts that were actually routed after completed orders.
The endpoint only returns payouts where the requested pharmacy is the payment receiver. With a single-pharmacy API key, this is your current pharmacy. With a group-wide API key, this is the pharmacy selected by `pharmacy_uid`.
Actual payment routing happens only when the pharmacy order is completed. `projected` payouts are indicative previews for paid physical-prescription orders based on the current order values and routing configuration, and can change before completion. When an order is completed, it **leaves the `projected` set** (open-order filter). It appears under `routed` only if a split-payment route was actually created — there is no persistent projected history after completion. Environments that do not run routing will therefore show completed orders disappearing from `projected` without a matching `routed` row. The same gap can happen when routing runs but is halted, for example when store credit exceeds the pharmacy's item-rest and shipping cut: `projected` still disappears at completion, and `routed` is missing or smaller than the preview.
## List Payouts
```bash theme={null}
GET /v1/external_pharmacy_api/payouts
```
Requires the `orders_read` permission.
Required for group-wide API keys. Single-pharmacy API keys are automatically scoped to their pharmacy.
Filter by payout status. Accepted values are `projected` and `routed`.
Search term. Matches `pharmacy_order_uid`, `pharmacy_order_name`, routed payout `provider_route_id`, and `routing_description`.
Unix timestamp for the start of the date range. For `routed` rows, this filters by `routing_created_at`. For `projected` rows, this filters by `pharmacy_order_created_at`.
Unix timestamp for the end of the date range. For `routed` rows, this filters by `routing_created_at`. For `projected` rows, this filters by `pharmacy_order_created_at`.
Field used for sorting. Accepted values are `pharmacy_order_created_at`, `routing_created_at`, and `amount`.
Sort direction. Accepted values are `asc` and `desc`.
Page number (0-indexed).
Number of items per page. Capped at a maximum of 200 — values above 200 are silently reduced to 200.
### Statuses
| Status | Description |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `projected` | Indicative preview for a paid physical-prescription order based on current order values and routing configuration. This can change before the order is completed. |
| `routed` | Persisted split-payment route created after pharmacy order completion. These values represent the routed amount recorded by RxScale. |
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/external_pharmacy_api/payouts?pharmacy_uid=ph-xyz&start_date=1711843200&end_date=1714521599&page=0&limit=20" \
-H "X-API-Key: your-api-key-here"
```
### Response
```json theme={null}
{
"data": [
{
"status": "routed",
"amount": 1299,
"currency": "EUR",
"component_type": "item_rest",
"routing_description": "Medication X 100mg rest amount",
"provider_route_id": "rt_abc123",
"pharmacy_order_uid": "po-abc123",
"pharmacy_order_name": "#1001",
"pharmacy_order_created_at": 1711899000,
"routing_created_at": 1711985400
},
{
"status": "routed",
"amount": 250,
"currency": "EUR",
"component_type": "item_markup",
"routing_description": "Medication X 100mg markup",
"provider_route_id": "rt_def456",
"pharmacy_order_uid": "po-abc123",
"pharmacy_order_name": "#1001",
"pharmacy_order_created_at": 1711899000,
"routing_created_at": 1711985400
},
{
"status": "projected",
"amount": 499,
"currency": "EUR",
"component_type": "shipping",
"routing_description": "DHL Standard",
"provider_route_id": null,
"pharmacy_order_uid": "po-def456",
"pharmacy_order_name": "#1002",
"pharmacy_order_created_at": 1711981200,
"routing_created_at": null
}
],
"totalRegistries": 3,
"totalPages": 1
}
```
### Response Fields
Payout rows for the requested page.
`projected` for indicative payouts on paid physical-prescription orders, or `routed` for persisted routes created after order completion.
Payout amount in cents, for example `1299` for EUR 12.99.
ISO 4217 currency code, for example `EUR`.
The payout component, such as `item_rest`, `item_markup`, or `shipping`.
Human-readable description of the routed or projected component.
Payment provider route identifier. This is populated for routed payouts when the provider returned an identifier, and `null` for projected payouts.
UID of the related pharmacy order.
Human-readable pharmacy order name, for example `#1001`.
Unix timestamp when the pharmacy order was created.
Unix timestamp when the split-payment route was created. This is `null` for `projected` payouts and populated for `routed` payouts.
Total number of payout rows matching the filters.
Total number of pages available for the current `limit`.
# Pharmacy Products
Source: https://docs.rxscale.com/api-reference/external-pharmacy/products
List the products your pharmacy carries, grouped from your SKUs
# Pharmacy Products
View the products your pharmacy carries, each with its pharmacy SKUs nested underneath. This is
a product-first view of the same underlying data as [Pharmacy SKUs](/api-reference/external-pharmacy/skus)
— use this endpoint when you want one row per product with its variants grouped together,
instead of one row per SKU.
## List Products
```bash theme={null}
GET /v1/external_pharmacy_api/pharmacy_products/
```
Page number (0-indexed)
Items per page. Capped at a maximum of 500 — values above 500 are silently reduced to 500.
Required for group-wide API keys
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/external_pharmacy_api/pharmacy_products/?page=0&limit=50" \
-H "X-API-Key: YOUR_API_KEY"
```
### Response
```json theme={null}
{
"data": [
{
"uid": "prod-789",
"display_name": "Medication X",
"short_name": "Med X",
"handle": "medication-x",
"image_url": "https://cdn.rxscale.com/products/medication-x.jpg",
"prescription_required": false,
"pharmacy_skus": [
{
"uid": "psku-abc123",
"price": 1299,
"stock": 50,
"external_id": "EXT-001",
"created_at": 1716200000,
"updated_at": 1716203600,
"deleted_at": null,
"sku": {
"uid": "sku-456",
"display_name": "Medication X 100mg",
"pzn": "12345678",
"digital": false,
"unit": "g",
"standard_selling_unit": 10.0
}
}
]
}
],
"totalRegistries": 42,
"totalPages": 1
}
```
The `price` field on each nested pharmacy SKU is in **euro cents** (e.g., `1299` = €12.99).
**Pagination counts products, not SKUs.** `totalRegistries` and `totalPages` reflect the number
of products in the response, regardless of how many SKUs are nested under each one. A product
with three SKUs still occupies a single row and counts once.
**Which products appear:** a product is listed when you have at least one SKU for it that is
visible to you. If your account is configured to hide SKUs of a particular type (digital or
physical — see [Pharmacy SKUs](/api-reference/external-pharmacy/skus)), SKUs of that hidden type
are omitted from the `pharmacy_skus` array entirely — they are not returned at all, even inside
a product that also has visible SKUs. A product whose SKUs are *all* hidden does not appear in
`data` at all, and is not counted in `totalRegistries`. Stock level has no effect on whether a
product is listed — a product with every SKU at stock `0` still appears.
# Pharmacy SKUs
Source: https://docs.rxscale.com/api-reference/external-pharmacy/skus
Manage pharmacy SKU stock levels and pricing
# Pharmacy SKUs
View and update your pharmacy's product stock levels, pricing, and external system IDs.
## List SKUs
```bash theme={null}
GET /v1/external_pharmacy_api/pharmacy_skus/
```
Page number (0-indexed)
Items per page. Capped at a maximum of 500 — values above 500 are silently reduced to 500.
Required for group-wide API keys
### Response
```json theme={null}
{
"data": [
{
"uid": "psku-abc123",
"external_id": "EXT-001",
"price": 1299,
"stock": 50,
"pharmacy": "pharmacy-123",
"sku": {
"uid": "sku-456",
"display_name": "Medication X 100mg",
"pzn": "12345678",
"product_uid": "prod-789",
"product_display_name": "Medication X",
"standard_selling_unit": 10.0,
"unit": "g",
"product_handle": "medication-x",
"digital": false
},
"created_at": 1716200000,
"updated_at": 1716203600,
"deleted_at": null
}
],
"totalRegistries": 150,
"totalPages": 3
}
```
The `price` field is in **euro cents** (e.g., `1299` = €12.99).
REST SKU responses deliberately omit `markup`, `priority`, and `type` (platform margin). Those fields may still appear on `pharmacy_sku_stock_updated` webhooks — treat webhook-only fields as opaque and do not expect them on `GET`/`PATCH` pharmacy SKUs.
**Hidden SKU types:** Your account may be configured to hide SKUs of a particular type (digital or physical). SKUs of a hidden type are automatically excluded from this listing — they will not appear in the `data` array and are not counted in `totalRegistries`. This setting is off by default and is configured by your rxscale administrator. If you believe SKUs are missing from your listing, contact rxscale support.
## Update SKU
If your account is configured to hide a SKU type, any attempt to update a pharmacy SKU of that hidden type — via `PATCH /pharmacy_skus/{uid}`, `PATCH /pharmacy_skus/{uid}/stock`, or `PATCH /pharmacy_skus/{uid}/external_id` — returns `404 Not Found`. Check that the SKU appears in your `GET /pharmacy_skus` listing before attempting to update it.
Update price, stock, and/or external\_id for a pharmacy SKU.
```bash theme={null}
PATCH /v1/external_pharmacy_api/pharmacy_skus/{pharmacy_sku_uid}
```
### Request Body
All fields are optional — include only the fields you want to update.
```json theme={null}
{
"price": 1499,
"stock": 25,
"external_id": "EXT-002"
}
```
Stock values may be negative (for example, `-40`) to represent oversold inventory in your own system.
## Update Stock Only
A convenience endpoint to update only the stock level.
```bash theme={null}
PATCH /v1/external_pharmacy_api/pharmacy_skus/{pharmacy_sku_uid}/stock
```
```json theme={null}
{
"stock": 100
}
```
## Update External ID
Link a pharmacy SKU to your external system.
```bash theme={null}
PATCH /v1/external_pharmacy_api/pharmacy_skus/{pharmacy_sku_uid}/external_id
```
```json theme={null}
{
"external_id": "YOUR-SYSTEM-ID"
}
```
# Webhooks
Source: https://docs.rxscale.com/api-reference/external-pharmacy/webhooks
Register webhooks for real-time pharmacy order and stock notifications
# Webhooks (External Pharmacy API)
Register webhooks to receive real-time notifications when pharmacy orders are created, updated, or when stock levels change.
## List Webhooks
```bash theme={null}
GET /v1/external_pharmacy_api/webhooks/
```
### Response
```json theme={null}
[
{
"uid": "sub-abc123",
"target": "https://your-system.com/webhooks/rxscale",
"notification_type": "pharmacy_order_created"
}
]
```
## Register Webhook
```bash theme={null}
POST /v1/external_pharmacy_api/webhooks/
```
### Request Body
```json theme={null}
{
"target": "https://your-system.com/webhooks/rxscale",
"notification_type": "pharmacy_order_created"
}
```
### Response
```json theme={null}
{
"uid": "sub-abc123",
"target": "https://your-system.com/webhooks/rxscale",
"notification_type": "pharmacy_order_created",
"webhook_secret": "whsec_abc123..."
}
```
The `webhook_secret` is only returned once during creation. Store it securely — you'll need it to verify webhook signatures. See [Webhook Security](/webhooks/security) for details.
## Remove Webhook
```bash theme={null}
DELETE /v1/external_pharmacy_api/webhooks/{subscription_uid}
```
Returns `204 No Content` on success.
## Available Event Types
| Event Type | Description |
| ---------------------------- | --------------------------------------------------------------------------------------- |
| `pharmacy_order_created` | A new pharmacy order has been created |
| `pharmacy_order_updated` | An existing order changed, including status updates and shipments added by the pharmacy |
| `pharmacy_sku_stock_updated` | Stock level changed for one of your SKUs |
See [Webhook Events](/webhooks/events) for full payload details.
# Anamnesis
Source: https://docs.rxscale.com/api-reference/management/anamnesis
Connect an anamnesis to a patient
# Anamnesis
An anamnesis is a completed questionnaire submission. It is often collected before you know
which customer it belongs to -- for example when a patient fills in a form before an order
exists. This endpoint attaches one to a patient.
## Connect an Anamnesis to a Patient
Requires an API key with the `anamnesis:connect_patient` permission.
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/anamnesis/patient-connections" \
-H "X-API-Key: {api_key}" \
-H "Content-Type: application/json" \
-d '{
"shop_identifier": "my-shop",
"shop_customer_id": "cust-123",
"anamnesis_uid": "an-abc123"
}'
```
**Response** (`200 OK`):
```json theme={null}
{
"status": "success"
}
```
### Identifying the patient
`shop_identifier` and `shop_customer_id` are always required. Together they name the customer
in your shop, the same pair used by the wallet pass and patient endpoints. The shop must
belong to your organisation.
### Identifying the anamnesis
Supply **exactly one** of the three addressing modes below. The last one is a pair: both
halves are required together, and supplying only one is an error rather than a third way of
asking.
| Mode | Fields | Use when |
| ---------------------- | --------------------------------------------------- | ----------------------------------------------------------------- |
| Internal | `anamnesis_uid` | You already have the anamnesis UID from RxScale |
| External by UID | `external_submission_uid` | You submitted through the Anamnesis API and kept the returned UID |
| External by identifier | `external_identifier` **and** `provider_identifier` | You track submissions under your own identifier |
Both external modes convert the submission into an internal anamnesis first, then attach the
patient to it. Converting is repeatable: calling again reuses the anamnesis created the first
time rather than making a second one.
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/anamnesis/patient-connections" \
-H "X-API-Key: {api_key}" \
-H "Content-Type: application/json" \
-d '{
"shop_identifier": "my-shop",
"shop_customer_id": "cust-123",
"external_identifier": "your-submission-id",
"provider_identifier": "your-provider-id"
}'
```
Connecting the **same** patient again returns `200` and changes nothing, so it is safe to
retry a request whose response you did not receive.
An anamnesis can only be connected once. Connecting a **different** patient to an anamnesis
that is already attached returns `409` and leaves the original connection untouched.
### Error Responses
| Code | Meaning |
| ----- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | Validation error -- including no addressing mode, more than one, or only half of the `external_identifier` / `provider_identifier` pair |
| `401` | Missing or invalid API key |
| `403` | The API key lacks `anamnesis:connect_patient` |
| `404` | Shop, patient, anamnesis, external submission or provider not found, or not part of your organisation |
| `409` | The anamnesis is already connected to a different patient |
A `400` reports the offending fields:
```json theme={null}
{
"error": {
"_schema": [
"Supply exactly one of: anamnesis_uid, external_submission_uid, or external_identifier together with provider_identifier."
]
}
}
```
Anything belonging to another organisation returns `404`, exactly like something that does not
exist, so the response never confirms whether a record is real.
### After connecting
Once connected, the questionnaire answers are used to fill the patient's profile fields. The
anamnesis also appears on the patient in the RxScale interface.
# Doctors
Source: https://docs.rxscale.com/api-reference/management/doctors
List doctors and view prescription statistics via the Management API
# Doctors
Retrieve a list of doctors in your organisation and view their prescription statistics over a given time period.
## List Doctors
```bash theme={null}
GET /v1/management/doctors
```
Page number (0-indexed)
Number of doctors per page
**Required permission:** `doctor:read`
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/doctors?page=0&limit=25" \
-H "X-API-Key: your-api-key-here"
```
### Response
```json theme={null}
{
"doctors": [
{
"uid": "doc-abc123",
"display_name": "Dr. Schmidt"
},
{
"uid": "doc-def456",
"display_name": "Dr. Meier"
}
],
"total": 15,
"totalPages": 1
}
```
## Get Doctor Statistics
Retrieve prescription statistics for a specific doctor over a time period.
```bash theme={null}
GET /v1/management/doctors/{doctor_uid}/statistics
```
The doctor UID
Start of time period (Unix timestamp in seconds)
End of time period (Unix timestamp in seconds)
**Required permission:** `doctor_statistics:read`
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/doctors/doc-abc123/statistics?from=1709251200&to=1711929600" \
-H "X-API-Key: your-api-key-here"
```
### Response
```json theme={null}
{
"uid": "doc-abc123",
"display_name": "Dr. Schmidt",
"prescription_statistics": {
"signed": 42,
"waiting_for_doctor": 3,
"rejected": 1
}
}
```
### Response Fields
| Field | Type | Description |
| ------------------------- | ------ | ------------------------------------- |
| `uid` | string | Doctor UID |
| `display_name` | string | Doctor display name |
| `prescription_statistics` | object | Prescription counts grouped by status |
### Error Responses
| Status Code | Description |
| ----------- | --------------------------------------------------- |
| `400` | Missing or invalid `from` / `to` parameters |
| `404` | Doctor not found or belongs to another organisation |
# Listing Requests
Source: https://docs.rxscale.com/api-reference/management/listing-requests
List, view, accept, and decline pharmacy listing requests via the Management API
# Listing Requests
Pharmacies can ask a shop to list a product they already carry. The Management API
exposes the organisation inbox for those requests: list them, inspect the submitted
fields, and record an accept or decline decision.
Accept and decline **record the decision only**. They do not create catalog rows
(`Product`, `SKU`, `ShopProduct`) and they do not push anything to Shopify or
WooCommerce.
All endpoints are scoped to the organisation that owns the API key. UIDs that
belong to another organisation are treated as not found.
Configuration of which fields a pharmacy must supply is done in the admin
interface, not on this API.
## Permissions
Listing-request endpoints are gated by two permissions:
* `listing_request:read` — required for all read (`GET`) endpoints.
* `listing_request:write` — required to accept or decline a request.
A key with `listing_request:write` is not automatically granted
`listing_request:read`; add both if you need to read and write. Contact your
RxScale account manager to adjust permissions.
All requests authenticate with the `X-API-Key` header. See
[Authentication](/authentication) for details.
## Request shape
There is **no top-level `pzn` field**. PZN is an ordinary extras key: a shop must
configure a `pzn` extras requirement before a pharmacy can send one, and the
value then appears under `extras.pzn` in the response.
`price_indication` is an integer in **minor units** (cents), matching pharmacy
SKU prices.
Attribute (`attr.*`) and PZN query filters are **not** available on the
Management API in v1. They are admin-only. Filter here with `shop_uid`,
`pharmacy_uid`, and `status` only.
## List Listing Requests
```bash theme={null}
GET /v1/management/listing-requests
```
Returns a paginated list of listing requests for the organisation.
**Required permission:** `listing_request:read`
Page number (0-indexed)
Number of listing requests per page
Filter by shop UID. Unknown shops for this organisation return 404.
Filter by pharmacy UID
Filter by status. One of `PENDING`, `ACCEPTED`, `DECLINED`, or `WITHDRAWN`.
#### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/listing-requests?page=0&limit=25&status=PENDING" \
-H "X-API-Key: your-api-key-here"
```
#### Response
```json theme={null}
{
"data": [
{
"uid": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"shop_uid": "a8c829ca-de1a-4b5e-9f6d-c1957d28aa4a",
"pharmacy_uid": "22c49130-90aa-4384-aff2-4551f227cab4",
"product_name": "Ibuprofen 400mg",
"price_indication": 1250,
"additional_notes": null,
"status": "PENDING",
"decision_note": null,
"decided_at": null,
"decided_by_user_uid": null,
"decided_by_identifier": null,
"extras": {
"pzn": {
"value": "01234567",
"value_normalized": "01234567",
"field_requirement_uid": "3f17e567-77c2-49e1-9eb2-2d8d901a4bb7",
"display_name": "PZN",
"field_type": "TEXT"
}
}
}
],
"totalRegistries": 1,
"totalPages": 1
}
```
## Get a Listing Request
```bash theme={null}
GET /v1/management/listing-requests/{listing_request_uid}
```
**Required permission:** `listing_request:read`
Listing request UID
#### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/listing-requests/7c9e6679-7425-40de-944b-e07fc1f90ae7" \
-H "X-API-Key: your-api-key-here"
```
#### Response
```json theme={null}
{
"uid": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"shop_uid": "a8c829ca-de1a-4b5e-9f6d-c1957d28aa4a",
"pharmacy_uid": "22c49130-90aa-4384-aff2-4551f227cab4",
"product_name": "Ibuprofen 400mg",
"price_indication": 1250,
"additional_notes": null,
"status": "PENDING",
"decision_note": null,
"decided_at": null,
"decided_by_user_uid": null,
"decided_by_identifier": null,
"extras": {
"pzn": {
"value": "01234567",
"value_normalized": "01234567",
"field_requirement_uid": "3f17e567-77c2-49e1-9eb2-2d8d901a4bb7",
"display_name": "PZN",
"field_type": "TEXT"
}
}
}
```
A missing UID, or a UID that belongs to another organisation, returns `404`.
## Accept a Listing Request
```bash theme={null}
POST /v1/management/listing-requests/{listing_request_uid}/accept
```
Records an accept decision. Does **not** create catalog products or push to a
storefront.
**Required permission:** `listing_request:write`
Listing request UID
Optional note stored with the decision
#### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/listing-requests/7c9e6679-7425-40de-944b-e07fc1f90ae7/accept" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{"decision_note": "Will list internally"}'
```
Only `PENDING` requests can be accepted. A request that is already decided
returns `400`.
## Decline a Listing Request
```bash theme={null}
POST /v1/management/listing-requests/{listing_request_uid}/decline
```
Records a decline decision.
**Required permission:** `listing_request:write`
Listing request UID
Optional note stored with the decision
#### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/listing-requests/7c9e6679-7425-40de-944b-e07fc1f90ae7/decline" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{"decision_note": "Already listed under a different SKU"}'
```
Only `PENDING` requests can be declined. A request that is already decided
returns `400`.
# Order Intake
Source: https://docs.rxscale.com/api-reference/management/order-intake
Create and manage orders from your own sales channel via the Management API
# Order Intake
If your organisation does not sell through Shopify, order intake lets you push orders into RxScale
directly from your own sales channel -- your own website, POS system, or any other system of
record. Everything downstream (prescriptions, pharmacy routing, fulfillment) works exactly the same
way it does for a Shopify order; only how the order gets into RxScale differs.
This is for **your own** integration, pushing orders into **your own** organisation. If you are a
telemedicine provider integrating into one of RxScale's customer shops, see the
[Public API](/api-reference/public/orders) instead.
## Before You Start
* **Required permission:** every endpoint on this page requires `order:write` on your API key.
* **A shop must exist first.** Orders are created against a `shop_identifier` that RxScale configures
for your organisation when the integration is set up. Contact your RxScale account manager if you
don't have one yet.
* **Order status for your own customers lives elsewhere.** This page only covers pushing orders in.
To check the status of an order as it moves through prescription review and pharmacy fulfillment,
use [`GET /v1/management/orders`](/api-reference/management/orders) -- the read endpoints already
documented on the Orders page.
## Four Operations
| Method | Endpoint | Effect |
| ------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `POST` | `/orders` | Create an order, including at least one fulfillment and its items |
| `POST` | `/orders/{order_external_id}/fulfillments` | Add a **new** fulfillment to an existing order |
| `PATCH` | `/orders/{order_external_id}` | Update item-free order fields (addresses, priority, customer email, shipping cost) |
| `POST` | `/orders/{order_external_id}/fulfillments/{fulfillment_external_id}/cancel` | Cancel a fulfillment, while it is still cancellable |
**Fulfillments are immutable.** There is no endpoint to change the items on a fulfillment once it
is created. To change what is dispensed, cancel the fulfillment and create a new one with the
corrected items -- this also starts a fresh prescription for the new items.
## Create an Order
```bash theme={null}
POST /v1/management/orders
```
**Required permission:** `order:write`
Creates an order together with its first fulfillment (or several fulfillments at once). Creation is
**strictly once per `external_id`** -- see [Retries and Duplicate Requests](#retries-and-duplicate-requests)
below.
### Request Body
| Field | Type | Required | Description |
| ------------------- | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `shop_identifier` | string | Yes | The identifier of your shop, as configured by RxScale |
| `external_id` | string | Yes | Your own unique id for this order. Creating an order with the same `external_id` twice is rejected -- see below |
| `name` | string | No | A human-readable order name/number, shown in the RxScale back office. Defaults to `external_id` |
| `customer` | object | Yes | See [Customer object](#customer-object) |
| `delivery_address` | object | Yes | See [Address object](#address-object) |
| `invoice_address` | object | No | See [Address object](#address-object). Defaults to `delivery_address` when omitted |
| `shipping_cost` | object | No | See [Money object](#money-object) |
| `shipping_methods` | array of strings | No | Free-text shipping method labels (e.g. `["standard"]`) |
| `priority` | integer | No | A priority hint used for internal queueing |
| `on_hold` | boolean | No | When `true`, the order is created on hold and does not proceed to a doctor or pharmacy automatically. Defaults to `false` |
| `hold_comment` | string | No | A note explaining why the order is on hold. Only meaningful together with `on_hold: true` |
| `doctor_uid` | string | No | Pre-assign a specific RxScale doctor to review the order's prescriptions. Must be a doctor in your own organisation; any other value is rejected with `404` |
| `pharmacy_uid` | string | No | Pre-assign a specific pharmacy for the whole order |
| `pharmacy_email` | string | No | An email address to notify alongside `pharmacy_uid` |
| `referral_scan_uid` | string | No | Attribution reference for a referral link scan that led to this order |
| `fulfillments` | array of objects | Yes | At least one. See [Fulfillment object](#fulfillment-object) |
#### Customer Object
| Field | Type | Required | Description |
| ------- | ------ | -------- | -------------------------------------------------------------------------------------------- |
| `id` | string | Yes | Your own stable id for this customer. Reused across orders to recognise a returning customer |
| `email` | string | No | The customer's email address |
#### Address Object
| Field | Type | Required | Description |
| -------------------- | ------ | -------- | ------------------------------------- |
| `first_name` | string | Yes | |
| `last_name` | string | Yes | |
| `street` | string | Yes | |
| `house_number` | string | Yes | |
| `zip_code` | string | Yes | |
| `city` | string | Yes | |
| `country` | string | Yes | 2-letter ISO country code (e.g. `DE`) |
| `additional_address` | string | No | Apartment number, care-of line, etc. |
| `province` | string | No | State/region, where applicable |
#### Money Object
RxScale never accepts decimal or floating-point amounts -- amounts are always **integers in the
currency's minor unit** (cents for EUR), the same convention used throughout the platform.
| Field | Type | Required | Description |
| ---------- | ------- | -------- | --------------------------------------------- |
| `amount` | integer | Yes | Amount in minor units, e.g. `1998` for €19.98 |
| `currency` | string | Yes | 3-letter ISO currency code (e.g. `EUR`) |
#### Fulfillment Object
A fulfillment is a group of items that ships and is prescribed together. Most orders have one; an
order with items from different pharmacies or with different readiness (e.g. one item ready to ship,
one still pending a prescription) has several.
| Field | Type | Required | Description |
| -------------------------- | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `external_id` | string | Yes | Your own unique id for this fulfillment |
| `items` | array of objects | Yes | At least one. See [Item object](#item-object) |
| `destination_pharmacy_uid` | string | No | Route this fulfillment to a specific pharmacy, bypassing automatic pharmacy selection |
| `location_id` | string | No | An alternate way to steer pharmacy routing, when your integration maps to RxScale-configured locations rather than pharmacy UIDs directly |
| `prescriptions` | array of objects | No | Signed PDFs to upload inline. See [Linking a Prescription](#linking-a-prescription) |
#### Item Object
| Field | Type | Required | Description |
| ------------------------ | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `external_id` | string | Yes | Your own unique id for this line, unique across the whole order (not just this fulfillment) |
| `sku_reference` | string | Yes | The SKU's variant identifier in your catalogue, as configured in RxScale |
| `product_reference` | string | No | The product identifier in your catalogue. Required whenever `sku_reference` alone does not uniquely resolve a product in your shop's configuration -- ask your RxScale contact if you're unsure whether your catalogue needs this |
| `quantity` | integer | Yes | Must be at least `1` |
| `total_paid` | object | Yes | See [Money object](#money-object). This is the amount the customer paid for this line -- RxScale holds product identity, you hold price truth |
| `prescription_uid` | string | No | Link to an existing signed prescription. See [Linking a Prescription](#linking-a-prescription) |
| `prescription_reference` | string | No | Link to a PDF uploaded inline on this fulfillment. See [Linking a Prescription](#linking-a-prescription) |
| `anamnesis_uid` | string | No | Link to a completed questionnaire response, for an RxScale doctor to review. See [Linking a Prescription](#linking-a-prescription) |
| `fulfillment_method` | string | No | A hint used to select the delivery type (e.g. shipping vs. pickup) when your shop has more than one configured |
### Linking a Prescription
A line item needs a prescription whenever its product requires one. There are four ways to satisfy
that, and every line uses exactly one of them:
Leave `prescription_uid`, `prescription_reference`, and `anamnesis_uid` all unset. Only valid for
products in your catalogue that are configured as not requiring a prescription.
Set `anamnesis_uid` to the uid of a completed questionnaire response for this customer. An
RxScale doctor reviews it and issues the prescription; the order moves to
`waiting for doctor` until they do.
Set `prescription_uid` to the uid of a prescription that is already **signed** -- for example,
one your integration obtained earlier through another RxScale flow. The prescription must
already belong to your organisation.
Add an entry to the fulfillment's own `prescriptions` array: `{"id": "your-reference",
"pdf_base64": "..."}`. The PDF is validated for a qualified electronic signature (QES) as part
of the request -- an unsigned or invalid PDF rejects the whole order, nothing is created. On the
line item, set `prescription_reference` to the same `id` you used in `prescriptions`. RxScale
turns the upload into a prescription and links it to the line automatically; from that point on
it behaves exactly like the previous option.
```json theme={null}
{
"external_id": "ff-1",
"items": [
{
"external_id": "line-1",
"sku_reference": "variant-123",
"product_reference": "prod-123",
"quantity": 1,
"total_paid": { "amount": 4500, "currency": "EUR" },
"prescription_reference": "rx-upload-1"
}
],
"prescriptions": [
{ "id": "rx-upload-1", "pdf_base64": "JVBERi0xLjQK..." }
]
}
```
A line cannot set both `prescription_uid` and `prescription_reference` -- pick one. A
`prescription_reference` that names no entry in this fulfillment's own `prescriptions` array is
rejected before anything is created.
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/orders" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"shop_identifier": "my-shop",
"external_id": "order-10231",
"customer": {
"id": "cust-4471",
"email": "patient@example.com"
},
"delivery_address": {
"first_name": "Ada",
"last_name": "Lovelace",
"street": "Hauptstraße",
"house_number": "1",
"zip_code": "10115",
"city": "Berlin",
"country": "DE"
},
"fulfillments": [
{
"external_id": "ff-1",
"items": [
{
"external_id": "line-1",
"sku_reference": "variant-123",
"product_reference": "prod-123",
"quantity": 2,
"total_paid": { "amount": 1998, "currency": "EUR" }
}
]
}
]
}'
```
### Response (201 Created)
```json theme={null}
{
"order_uid": "ord-abc123",
"external_id": "order-10231"
}
```
| Field | Type | Description |
| ------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `order_uid` | string | The RxScale-assigned order UID. Use it with [`GET /v1/management/orders/{order_uid}`](/api-reference/management/orders#get-order-details) to check on the order afterwards |
| `external_id` | string | Echoes the `external_id` you sent |
### Error Responses
| Status | Code | Description |
| ------ | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | `sku_not_found` | One or more items reference a `sku_reference`/`product_reference` combination not in your catalogue. The response also includes `missing_item_ids`, the `external_id` of each offending line. No order is created |
| `400` | *(none)* | The request body failed validation (missing/invalid field, an invalid inline PDF, a `prescription_reference` that names no upload, etc.). The response's `error` field describes what failed |
| `401` | *(none)* | Missing or invalid API key |
| `403` | *(none)* | Missing `order:write` permission |
| `404` | *(none)* | No shop found for `shop_identifier` in your organisation |
| `404` | *(none)* | `doctor_uid` names no doctor in your organisation. A doctor from another organisation and an unknown uid are answered identically |
| `409` | `order_already_exists` | An order with this `external_id` already exists. The response body carries the existing order's `order_uid` -- see [Retries and Duplicate Requests](#retries-and-duplicate-requests) |
When a request is rejected, **nothing is created** -- not the order, not a prescription from an
inline PDF, nothing. `external_id` stays free to retry with a corrected payload.
### Retries and Duplicate Requests
Order creation is strictly once per `external_id`: POSTing the same `external_id` a second time
always returns `409` with `code: "order_already_exists"`, even if the first request's response
never reached you (a timeout, a dropped connection). The `409` body carries the existing order's
`order_uid`, so recovering from a lost response needs no follow-up call -- just retry with the same
payload and read `order_uid` off the error:
```json theme={null}
{
"code": "order_already_exists",
"error": "Order order-10231 already exists for shop my-shop",
"order_uid": "ord-abc123"
}
```
Alternatively, the same information is available via a lookup:
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/orders?shop_identifier=my-shop&shop_order_external_id=order-10231" \
-H "X-API-Key: your-api-key-here"
```
This is the same [List Orders](/api-reference/management/orders#look-up-a-specific-shop-order)
endpoint already documented on the Orders page, filtered down to at most one result.
## Add a Fulfillment
```bash theme={null}
POST /v1/management/orders/{order_external_id}/fulfillments
```
Your `external_id` for the existing order
**Required permission:** `order:write`
Adds a **new** fulfillment (and its items) to an order that already exists. The request body is a
single [Fulfillment object](#fulfillment-object) -- not wrapped in an order. Use this when items in
the same order become available at different times, or need to be split across pharmacies.
A fulfillment `external_id` that already exists on this order is rejected: fulfillments cannot be
changed once created, only cancelled and replaced.
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/orders/order-10231/fulfillments" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"external_id": "ff-2",
"items": [
{
"external_id": "line-2",
"sku_reference": "variant-456",
"product_reference": "prod-456",
"quantity": 1,
"total_paid": { "amount": 2500, "currency": "EUR" }
}
]
}'
```
### Response (201 Created)
```json theme={null}
{
"order_uid": "ord-abc123",
"external_id": "ff-2"
}
```
### Error Responses
| Status | Code | Description |
| ------ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | `sku_not_found` | One or more items reference an unknown SKU. `missing_item_ids` lists the offending `external_id`s |
| `400` | *(none)* | The request body failed validation |
| `401` | *(none)* | Missing or invalid API key |
| `403` | *(none)* | Missing `order:write` permission |
| `404` | *(none)* | No order found for `external_id` in your organisation |
| `409` | `fulfillment_immutable` | A fulfillment with this `external_id` already exists on the order |
| `409` | `order_not_updatable` | The order is in a status that no longer accepts new fulfillments (for example, `waiting for pharmacy`). A **completed** order is the exception: adding a fulfillment to it reopens it automatically instead of returning an error |
## Update an Order
```bash theme={null}
PATCH /v1/management/orders/{order_external_id}
```
Your `external_id` for the existing order
**Required permission:** `order:write`
Updates order-level fields that are not tied to items. **Items are never accepted here** -- they
always arrive inside a fulfillment (see [Add a Fulfillment](#add-a-fulfillment)); a request body
containing an `items` key is rejected.
### Request Body
All fields are optional; send only what you want to change.
| Field | Type | Description |
| ------------------ | ------- | ------------------------------------- |
| `delivery_address` | object | See [Address object](#address-object) |
| `invoice_address` | object | See [Address object](#address-object) |
| `priority` | integer | |
| `customer_email` | string | |
| `shipping_cost` | object | See [Money object](#money-object) |
### Example Request
```bash theme={null}
curl -X PATCH "https://api.rxscale.com/v1/management/orders/order-10231" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"delivery_address": {
"first_name": "Ada",
"last_name": "Lovelace",
"street": "Hauptstraße",
"house_number": "2",
"zip_code": "10115",
"city": "Berlin",
"country": "DE"
}
}'
```
### Response (200 OK)
```json theme={null}
{
"status": "updated"
}
```
### Error Responses
| Status | Code | Description |
| ------ | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | *(none)* | The request body failed validation, or contained an `items` key |
| `401` | *(none)* | Missing or invalid API key |
| `403` | *(none)* | Missing `order:write` permission |
| `404` | *(none)* | No order found for `external_id` in your organisation |
| `409` | `order_not_updatable` | The order is in a status that no longer accepts updates (for example, already completed) |
| `409` | `address_locked` | You're changing `delivery_address` or `invoice_address` on an order that already has an active pharmacy order. Addresses lock once a pharmacy is preparing the shipment |
## Cancel a Fulfillment
```bash theme={null}
POST /v1/management/orders/{order_external_id}/fulfillments/{fulfillment_external_id}/cancel
```
Your `external_id` for the order
Your `external_id` for the fulfillment to cancel
**Required permission:** `order:write`
Cancels a fulfillment. This is the only way to change what a fulfillment contains -- cancel it, then
[add a new fulfillment](#add-a-fulfillment) with the corrected items.
Cancellation is only possible while the fulfillment's prescription (if it has one) has not yet
reached a final state, and while no pharmacy is actively processing it. Once a doctor signs or
declines the prescription, or a pharmacy starts working the order, cancelling is refused.
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/orders/order-10231/fulfillments/ff-2/cancel" \
-H "X-API-Key: your-api-key-here"
```
### Response (200 OK)
```json theme={null}
{
"status": "cancelled"
}
```
### Error Responses
| Status | Code | Description |
| ------ | -------------------------------------- | ------------------------------------------------------------------------------------------- |
| `401` | *(none)* | Missing or invalid API key |
| `403` | *(none)* | Missing `order:write` permission |
| `404` | *(none)* | No order or fulfillment found for the given `external_id`s in your organisation |
| `409` | `prescription_already_finished` | The fulfillment's prescription is already signed or declined and can no longer be cancelled |
| `409` | `fulfillment_locked_by_pharmacy_order` | A pharmacy is already actively processing this fulfillment |
## Where Order Status Lives
This page covers pushing orders **into** RxScale. To track an order after that -- its status, which
prescription is attached, which pharmacy order was created -- use
[`GET /v1/management/orders`](/api-reference/management/orders) and
[`GET /v1/management/orders/{order_uid}`](/api-reference/management/orders#get-order-details),
already documented on the Orders page. If you are instead a telemedicine provider tracking orders
you placed through the Public API, order status for that flow lives in the
[Public API](/api-reference/public/orders) -- not here.
# Orders
Source: https://docs.rxscale.com/api-reference/management/orders
List and view order details via the Management API
# Orders
Retrieve order information including items, fulfillment orders, and shop data.
This page covers **reading** order data. If your organisation sells through a channel other than
Shopify and needs to push orders into RxScale, see [Order Intake](/api-reference/management/order-intake).
## List Orders
```bash theme={null}
GET /v1/management/orders
```
Page number (0-indexed)
Number of orders per page
Filter orders created at or after this Unix timestamp
Filter orders created at or before this Unix timestamp
Filter orders updated at or after this Unix timestamp
Filter orders updated at or before this Unix timestamp
Filter by the shop's identifier (the string you configured when the shop
was set up). Only orders whose linked shop belongs to your organisation
and has this identifier are returned.
Filter by the external order id assigned by the shop or e-commerce
platform (for example, the Shopify order number). Combine with
`shop_identifier` to look up a specific shop order.
Free-text search across the Shopify order name, the pharmacy order name
(e.g. `PO-XXXX-XXXX-XXXX`), and the patient's full name. The same value
is also matched as an exact UID against the order, its prescriptions,
and its pharmacy orders, so you can paste an identifier directly. A
leading `#` is stripped before the UID lookup so values copied from the
admin interface (e.g. `#ord_…`) resolve as expected.
**Required permission:** `order:read`
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/orders?page=0&limit=25&updated_at_from=1712300000" \
-H "X-API-Key: your-api-key-here"
```
### Look Up a Specific Shop Order
Combine `shop_identifier` and `shop_order_external_id` to fetch the single
order that matches both. The response shape is unchanged (a paginated
list), but the result contains at most one order.
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/orders?shop_identifier=my-shop&shop_order_external_id=EXT-12345" \
-H "X-API-Key: your-api-key-here"
```
### Response
```json theme={null}
{
"orders": [
{
"uid": "ord-abc123",
"status": "waiting for doctor",
"overall_status": "pending",
"created_at": 1712300000,
"updated_at": 1712400000,
"shop_order": {
"uid": "so-789",
"external_id": "EXT-12345",
"shop": {
"uid": "shop-456",
"identifier": "my-shop"
}
},
"items": [
{
"uid": "oi-001",
"sku_uid": "sku-456",
"sku_display_name": "Medication X 100mg",
"sku_pzn": "12345678",
"amount": 2,
"prescription_uid": "px-001"
}
],
"fulfillment_orders": [
{
"uid": "fo-001",
"status": "OPEN",
"external_id": "ext-fo-1",
"items": [
{
"uid": "foi-001",
"order_item_uid": "oi-001",
"status": "OPEN",
"amount": 2
}
]
}
],
"prescriptions": [
{
"uid": "px-001",
"status": "signed",
"doctor_uid": "doc-456",
"doctor_name": "Dr. Schmidt"
}
]
}
],
"totalRegistries": 150,
"totalPages": 6
}
```
### Response Fields
| Field | Type | Description |
| ----------------------------------- | ------- | -------------------------------------------------------------- |
| `orders[].uid` | string | Order UID |
| `orders[].status` | string | Order status |
| `orders[].overall_status` | string | Computed overall status |
| `orders[].created_at` | integer | Creation timestamp (Unix) |
| `orders[].updated_at` | integer | Last update timestamp (Unix) |
| `orders[].shop_order` | object | Shop and external order reference |
| `orders[].items` | array | Order items with SKU info |
| `orders[].items[].sku_display_name` | string | SKU display name |
| `orders[].items[].sku_pzn` | string | Pharmazentralnummer |
| `orders[].items[].amount` | integer | Quantity |
| `orders[].items[].prescription_uid` | string | Linked prescription (only with `prescription:read`) |
| `orders[].fulfillment_orders` | array | Fulfillment orders with status and items |
| `orders[].prescriptions` | array | Prescriptions with doctor info (only with `prescription:read`) |
| `totalRegistries` | integer | Total number of orders |
| `totalPages` | integer | Total number of pages |
## Get Order Details
```bash theme={null}
GET /v1/management/orders/{order_uid}
```
The order UID
**Required permission:** `order:read`
Returns the same structure as a single entry in the list response.
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/orders/ord-abc123" \
-H "X-API-Key: your-api-key-here"
```
## Prescription Data
Prescription data is only included when your API key also has the `prescription:read` permission. Without it:
* The `prescriptions` array is returned as `[]`
* The `prescription_uid` field is removed from order items
If you need prescription details, ensure your API key has both `order:read` and `prescription:read` permissions. Contact your RxScale account manager to adjust permissions.
# Overview
Source: https://docs.rxscale.com/api-reference/management/overview
Management API for organisation-level access
# Management API
The Management API provides organisation-level access to orders, prescriptions, products, doctors, patients, waiting room, listing requests, wallet passes, and webhook subscriptions. It is designed for back-office integrations and internal tooling.
## Base Path
```
/v1/management
```
## Interactive API Documentation (Swagger)
A live Swagger UI is available for exploring and testing endpoints directly in your browser:
```
https://api.rxscale.com/v1/management/apidocs
```
The Swagger UI lets you try out API calls interactively. Authenticate with your API key to test against real data.
## Authentication
All endpoints require an API key via the `X-API-Key` header. Management API keys are scoped to an **organisation** and can access data for all entities within that organisation. See [Authentication](/authentication) for details.
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/health/" \
-H "X-API-Key: your-api-key-here"
```
## Available Endpoints
| Method | Endpoint | Description |
| -------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `GET` | `/orders` | List orders (paginated) |
| `GET` | `/orders/{order_uid}` | View order details |
| `POST` | `/orders` | Create an order from your own sales channel ([Order Intake](/api-reference/management/order-intake)) |
| `POST` | `/orders/{external_id}/fulfillments` | Add a new fulfillment to an existing order |
| `PATCH` | `/orders/{external_id}` | Update item-free order fields |
| `POST` | `/orders/{external_id}/fulfillments/{fulfillment_external_id}/cancel` | Cancel a fulfillment |
| `GET` | `/prescriptions/{prescription_uid}` | View prescription details |
| `GET` | `/products` | List products with SKUs |
| `GET` | `/products/{product_uid}` | View product details |
| `GET` | `/doctors` | List doctors |
| `GET` | `/doctors/{doctor_uid}/statistics` | Get doctor prescription statistics |
| `GET` | `/patients?email=...` | Search patient by email |
| `GET` | `/patients/{patient_uid}` | View patient profile |
| `GET` | `/patients/{patient_uid}/intent/{intent}` | Check patient intent |
| `POST` | `/waiting-room/register` | Register patient for waiting room |
| `GET` | `/waiting-room/{queue_uid}/status` | Check queue status |
| `DELETE` | `/waiting-room/{queue_uid}` | Cancel queue registration |
| `POST` | `/review-links` | Create a single-use review link for a pharmacy order |
| `GET` | `/review-links` | List review links |
| `DELETE` | `/review-links/{uid}` | Revoke a review link |
| `GET` | `/listing-requests` | List listing requests (paginated) |
| `GET` | `/listing-requests/{uid}` | View a listing request |
| `POST` | `/listing-requests/{uid}/accept` | Accept a pending listing request |
| `POST` | `/listing-requests/{uid}/decline` | Decline a pending listing request |
| `GET` | `/wallet-passes/templates` | List wallet pass templates |
| `GET` | `/wallet-passes` | List wallet passes |
| `POST` | `/wallet-passes/verify` | Verify a scanned wallet pass |
| `POST` | `/wallet-passes/push-notifications` | Send push notifications |
| `GET` | `/notification-subscriptions` | List webhook subscriptions |
| `POST` | `/notification-subscriptions` | Register webhook subscription |
| `DELETE` | `/notification-subscriptions` | Remove webhook subscription |
| `POST` | `/notification-subscriptions/test` | Test webhook delivery |
## Required Permissions
| Endpoint | Required Permission |
| ------------------------------------------------------------------------------------ | ------------------------------------- |
| View orders | `order:read` |
| Create/update/cancel orders ([Order Intake](/api-reference/management/order-intake)) | `order:write` |
| View prescriptions | `prescription:read` |
| List products | `product:read` |
| List doctors | `doctor:read` |
| Doctor statistics | `doctor_statistics:read` |
| View patients | `patient:read` |
| Register / cancel waiting room | `waiting_room:write` |
| View waiting room status | `waiting_room:read` |
| Create / revoke review links | `review_link:write` |
| List review links | `review_link:read` |
| List wallet pass templates | `wallet_pass_template:read` |
| List wallet passes | `wallet_pass:read` |
| Verify a scanned wallet pass | `wallet_pass:verify` |
| Send push notifications | `wallet_pass_push_notification:write` |
| List / view listing requests | `listing_request:read` |
| Accept / decline listing requests | `listing_request:write` |
Webhook subscription endpoints (notification-subscriptions) do not require a specific permission beyond a valid Management API key.
# Patients
Source: https://docs.rxscale.com/api-reference/management/patients
Search and view patient profiles via the Management API
# Patients
Search for patients by email address, view patient profiles, and check patient intent status.
## Create or Resolve Patient
Create a patient profile for a shop customer, or return the existing profile if the `shop_uid` and `shop_customer_id` pair already exists. This is the supported patient creation flow for API-key integrations that later use Scheduling.
```bash theme={null}
POST /v1/management/patients
```
**Required permission:** `patient:write`
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/patients" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"shop_uid": "shop_123",
"shop_customer_id": "customer_456",
"email": "max@example.com",
"fields": [
{
"field": "first_name",
"value": "Max",
"source": "shop"
}
]
}'
```
UID of the shop the customer belongs to
The shop's identifier for the customer
Optional patient email stored on the shop-patient mapping and used for patient lookup/search.
Optional patient profile fields to set for the shop customer.
```json theme={null}
{
"patient_profile_uid": "pp-abc123"
}
```
## Search Patient by Email
Find a patient by their email address. Returns the most recently created patient whose shop-patient email or order email matches the given email.
```bash theme={null}
GET /v1/management/patients?email={email}
```
The email address to search for
**Required permission:** `patient:read`
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/patients?email=max@example.com" \
-H "X-API-Key: your-api-key-here"
```
### Response
```json theme={null}
{
"uid": "pp-abc123",
"data": {
"display_name": "Max Mustermann",
"email": "max@example.com",
"date_of_birth": "1990-01-15"
}
}
```
### Error Responses
| Status Code | Description |
| ----------- | ------------------------------------- |
| `400` | Missing or invalid email parameter |
| `404` | No patient found with the given email |
## Get Patient Profile
Retrieve a patient profile by UID.
```bash theme={null}
GET /v1/management/patients/{patient_uid}
```
The patient UID
**Required permission:** `patient:read`
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/patients/pp-abc123" \
-H "X-API-Key: your-api-key-here"
```
### Response
```json theme={null}
{
"uid": "pp-abc123",
"data": {
"display_name": "Max Mustermann",
"email": "max@example.com",
"date_of_birth": "1990-01-15"
}
}
```
## Check Patient Intent
Check the intent return code for a patient. Returns a code indicating when the last signed submission for this intent was made.
```bash theme={null}
GET /v1/management/patients/{patient_uid}/intent/{intent}
```
The patient UID
The intent identifier
**Required permission:** `patient:read`
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/patients/pp-abc123/intent/consultation" \
-H "X-API-Key: your-api-key-here"
```
### Response
```json theme={null}
{
"return_code": "VALID"
}
```
### Return Code Values
| Code | Description |
| ----------- | ---------------------------------------------------------- |
| `VALID` | The patient has a valid, recent submission for this intent |
| `EXPIRED` | The patient's last submission has expired |
| `NOT_FOUND` | No submission found for this intent |
## Delete Patient Profile Field
Delete a patient's value for a single profile field.
```bash theme={null}
DELETE /v1/management/patients/{patient_uid}/patient-profile-fields/{field_key}
```
The patient UID
Key of the profile field to delete the patient's value for
The field key is the same value you send in `fields[].field` when setting a value via [Create or Resolve Patient](#create-or-resolve-patient) -- for example `first_name`. Deleting a value therefore uses exactly the identifier you used to set it; no separate lookup is needed.
**Required permission:** `patient_profile_field:delete`
This is a **hard delete**. The field value is removed from the patient's profile, not archived or soft-deleted. An internal audit log entry recording the deletion (naming the API key that performed it) is written as part of the same call -- afterwards, that log entry is the only remaining record that the value ever existed.
### Example Request
```bash theme={null}
curl -X DELETE "https://api.rxscale.com/v1/management/patients/pp-abc123/patient-profile-fields/first_name" \
-H "X-API-Key: your-api-key-here"
```
### Response (200 OK)
```json theme={null}
{
"message": "Field deleted successfully"
}
```
### Error Responses
| Status Code | Description |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401` | Missing or invalid API key |
| `403` | Missing `patient_profile_field:delete` permission |
| `404` | Patient not found, belongs to another organisation, the shop does not define a field with this key, or the patient does not have a value set for it |
# Prescriptions
Source: https://docs.rxscale.com/api-reference/management/prescriptions
View prescription details via the Management API
# Prescriptions
Retrieve prescription information including doctor data and status.
## Get Prescription Details
```bash theme={null}
GET /v1/management/prescriptions/{prescription_uid}
```
The prescription UID
**Required permission:** `prescription:read`
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/prescriptions/px-abc123" \
-H "X-API-Key: your-api-key-here"
```
### Response
```json theme={null}
{
"uid": "px-abc123",
"status": "signed",
"doctor": {
"uid": "doc-456",
"display_name": "Dr. Schmidt"
},
"rendered": true
}
```
### Response Fields
| Field | Type | Description |
| --------------------- | ------- | ------------------------------------------------------------------------------- |
| `uid` | string | Unique identifier for the prescription |
| `status` | string | Current prescription status (e.g. `signed`, `waiting_for_doctor`) |
| `doctor` | object | The prescribing doctor |
| `doctor.uid` | string | Doctor UID |
| `doctor.display_name` | string | Doctor display name |
| `rendered` | boolean | `true` when a rendered PDF is available for the prescription, `false` otherwise |
### Error Responses
| Status Code | Description |
| ----------- | --------------------------------------------------------- |
| `403` | Missing `prescription:read` permission |
| `404` | Prescription not found or belongs to another organisation |
## Externally Signed Prescriptions
Prescriptions whose items were created with `_skip_validation` (or `_rxscale_skip_validation`) on the Shopify line item, on that item's line item group (for bundles), or in the order-level additional details — i.e. items without an attached anamnesis — can be signed *outside* the rxscale platform and then registered for fulfilment via the Management API. When more than one level is present, RxScale resolves the setting with a three-level fallback: the line item's own value wins first, then the value on its line item group, then the order-level value.
The flow consists of **two API calls**:
1. `POST /v1/management/prescriptions/{prescription_uid}/render` – asks rxscale to render the unsigned prescription PDF. The endpoint returns immediately; the PDF is produced asynchronously and becomes available as `prescription.file`.
2. `POST /v1/management/prescriptions/{prescription_uid}/external-sign` – once the PDF is available, mark the prescription as `EXTERNALLY_SIGNED`, copy the PDF into the signed bucket, and dispatch it to the pharmacy.
Both endpoints require the `prescription:external_sign` permission.
Only prescriptions where **every** item lacks an `anamnesis_uid` can be externally signed. Mixed prescriptions are rejected with `409 Conflict`.
## Render Prescription PDF
```bash theme={null}
POST /v1/management/prescriptions/{prescription_uid}/render
```
The prescription UID
**Required permission:** `prescription:external_sign`
Triggers asynchronous rendering of the unsigned prescription PDF. The endpoint publishes a `prescription.render` event and returns `202 Accepted`. Poll `GET /v1/management/prescriptions/{prescription_uid}` (or wait for a webhook) until `file` is populated before calling external-sign.
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/prescriptions/px-abc123/render" \
-H "X-API-Key: your-api-key-here"
```
### Response (202 Accepted)
```json theme={null}
{
"uid": "px-abc123",
"message": "Prescription rendering triggered"
}
```
### Error Responses
| Status Code | Description |
| ----------- | --------------------------------------------------------- |
| `403` | Missing `prescription:external_sign` permission |
| `404` | Prescription not found or belongs to another organisation |
## External-Sign Prescription
```bash theme={null}
POST /v1/management/prescriptions/{prescription_uid}/external-sign
```
The prescription UID
**Required permission:** `prescription:external_sign`
Marks an already-rendered prescription as `EXTERNALLY_SIGNED`. The endpoint:
1. Verifies the prescription is in `WAITING_FOR_DOCTOR` status, all items lack an anamnesis, and the rendered `file` is present.
2. Copies the PDF from the unsigned bucket to the signed-prescription bucket.
3. Updates the prescription status to `EXTERNALLY_SIGNED` and writes a `PrescriptionLog` entry attributed to the calling API key.
4. Publishes a `pharmacy_manager.send_prescription` event so the prescription is dispatched to the pharmacy.
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/prescriptions/px-abc123/external-sign" \
-H "X-API-Key: your-api-key-here"
```
### Response (200 OK)
```json theme={null}
{
"uid": "px-abc123",
"status": "EXTERNALLY_SIGNED"
}
```
### Error Responses
| Status Code | Description |
| ----------- | ----------------------------------------------------------------------------------------------------------------------- |
| `403` | Missing `prescription:external_sign` permission |
| `404` | Prescription not found or belongs to another organisation |
| `409` | Prescription is not in `WAITING_FOR_DOCTOR` status, has at least one item with an anamnesis, or has no rendered PDF yet |
# Products
Source: https://docs.rxscale.com/api-reference/management/products
List products and SKUs via the Management API
# Products
Retrieve a paginated list of all products for your organisation, including connected SKUs, shop SKUs, shop products, and pharmacy SKUs.
## List Products
```bash theme={null}
GET /v1/management/products
```
Page number (0-indexed)
Number of products per page
**Required permission:** `product:read`
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/products?page=0&limit=25" \
-H "X-API-Key: your-api-key-here"
```
### Response
```json theme={null}
{
"products": [
{
"uid": "prod-abc123",
"display_name": "Medication X",
"skus": [
{
"uid": "sku-456",
"display_name": "Medication X 100mg",
"pzn": "12345678",
"shop_skus": [
{
"uid": "ssku-789",
"shop_uid": "shop-001",
"external_id": "shopify-variant-123"
}
],
"pharmacy_skus": [
{
"uid": "psku-012",
"pharmacy_uid": "ph-xyz",
"price": 1299,
"stock": 50,
"external_id": "EXT-001"
}
]
}
],
"shop_products": [
{
"uid": "sp-345",
"shop_uid": "shop-001",
"external_id": "shopify-product-456"
}
]
}
],
"totalRegistries": 42,
"totalPages": 2
}
```
### Response Fields
| Field | Type | Description |
| --------------------------------- | ------- | ---------------------------------------------- |
| `products` | array | List of product objects |
| `products[].uid` | string | Product UID |
| `products[].display_name` | string | Product display name |
| `products[].skus` | array | SKUs belonging to this product |
| `products[].skus[].pzn` | string | Pharmazentralnummer (German pharmaceutical ID) |
| `products[].skus[].pharmacy_skus` | array | Pharmacy-specific SKU data (price, stock) |
| `totalRegistries` | integer | Total number of products |
| `totalPages` | integer | Total number of pages for the given limit |
The `price` field on pharmacy SKUs is in **euro cents** (e.g., `1299` = 12.99 EUR).
## Get Product Details
```bash theme={null}
GET /v1/management/products/{product_uid}
```
The product UID
**Required permission:** `product:read`
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/products/prod-abc123" \
-H "X-API-Key: your-api-key-here"
```
### Response
Returns the same product structure as the list endpoint, but for a single product including all connected SKUs, shop SKUs, shop products, and pharmacy SKUs.
```json theme={null}
{
"uid": "prod-abc123",
"display_name": "Medication X",
"skus": [...],
"shop_products": [...]
}
```
# Review Links
Source: https://docs.rxscale.com/api-reference/management/review-links
Create single-use, expiring links a pharmacist can open to review a pharmacy order and accept or decline its prescription
# Review Links
Review links let you share a single pharmacy order with a pharmacist who does **not** have a portal login. You create a link over a pharmacy order and send the resulting URL to the pharmacist. When they open it, they see the shop, its icon, and the order's items, and can either **accept and download** the signed prescription PDF or **decline** the order.
Each link is:
* **Single-use** -- once the pharmacist accepts or declines, the link is consumed and cannot be opened again.
* **Expiring** -- links expire after a configurable number of days (7 by default).
* **Revocable** -- you can revoke an unused link at any time.
The plaintext token (and the ready-to-share `url` built from it) is returned **only once**, in the response to the create call. It is stored hashed and cannot be retrieved again. If you lose it, revoke the link and create a new one.
## Create Review Link
Create a single-use review link over a pharmacy order.
```bash theme={null}
POST /v1/management/review-links
```
**Required permission:** `review_link:write`
### Request Body
```json theme={null}
{
"pharmacy_order_uid": "po-abc123",
"expires_in_days": 7,
"note": "Please review before end of day"
}
```
| Field | Type | Required | Description |
| -------------------- | ------- | -------- | ----------------------------------------------------------------------------------- |
| `pharmacy_order_uid` | string | Yes | UID of the pharmacy order the link is created over |
| `expires_in_days` | integer | No | Days until the link expires. Must be between `1` and `90`. Defaults to `7` |
| `note` | string | No | Free-text note stored on the link (max 255 characters). Not shown to the pharmacist |
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/review-links" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"pharmacy_order_uid": "po-abc123",
"expires_in_days": 7,
"note": "Please review before end of day"
}'
```
### Response (201 Created)
```json theme={null}
{
"uid": "rl-def456",
"token": "kQ8mZ3nR7wF1sT9vB2xY6pJ4hL0aD5cG8eN...",
"url": "https://api.rxscale.com/v1/pharmacy-api-v1/review/kQ8mZ3nR7wF1sT9vB2xY6pJ4hL0aD5cG8eN...",
"expires_at": 1735689600
}
```
### Response Fields
| Field | Type | Description |
| ------------ | ------- | ---------------------------------------------------------------------- |
| `uid` | string | Unique identifier for the review link. Use it to revoke the link later |
| `token` | string | The plaintext link token. **Returned only once** |
| `url` | string | The ready-to-share URL a pharmacist opens. **Returned only once** |
| `expires_at` | integer | Unix timestamp (seconds) at which the link expires |
Save the `token` and `url` from this response immediately. They cannot be retrieved again -- listing links never returns the token.
### Error Responses
| Status Code | Description |
| ----------- | ----------------------------------------------------------------------------------- |
| `400` | The pharmacy order has no downloadable prescription, or the request body is invalid |
| `401` | Missing or invalid API key |
| `403` | Missing `review_link:write` permission |
| `404` | Pharmacy order not found for this organisation |
## List Review Links
List review links for your organisation, optionally filtered to a single pharmacy order. The response includes **revoked** links and their derived status, but **never** the token.
```bash theme={null}
GET /v1/management/review-links
```
**Required permission:** `review_link:read`
### Query Parameters
Restrict the results to a single pharmacy order. When omitted, all review links for the organisation are returned.
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/review-links?pharmacy_order_uid=po-abc123" \
-H "X-API-Key: your-api-key-here"
```
### Response (200 OK)
```json theme={null}
{
"data": [
{
"uid": "rl-def456",
"pharmacy_order_uid": "po-abc123",
"status": "consumed",
"expires_at": 1735689600,
"consumed_at": 1735600000,
"decision": "accept",
"note": "Please review before end of day",
"created_at": 1735084800,
"visit_count": 2
},
{
"uid": "rl-ghi789",
"pharmacy_order_uid": "po-abc123",
"status": "active",
"expires_at": 1736294400,
"consumed_at": null,
"decision": null,
"note": null,
"created_at": 1735689600,
"visit_count": 0
}
]
}
```
### Response Fields
Each object in the `data` array has the following fields:
| Field | Type | Description |
| -------------------- | --------------- | ------------------------------------------------------------------------------ |
| `uid` | string | Unique identifier for the review link |
| `pharmacy_order_uid` | string | UID of the pharmacy order the link was created over |
| `status` | string | Current status: `active`, `consumed`, `expired`, or `revoked` |
| `expires_at` | integer | Unix timestamp (seconds) at which the link expires |
| `consumed_at` | integer \| null | Unix timestamp (seconds) at which the link was used, or `null` if still unused |
| `decision` | string \| null | The pharmacist's decision: `accept`, `decline`, or `null` if not yet used |
| `note` | string \| null | The note stored on the link, or `null` |
| `created_at` | integer | Unix timestamp (seconds) at which the link was created |
| `visit_count` | integer | Number of times the link page has been opened |
The `status` field is derived and takes the following values:
| Status | Meaning |
| ---------- | -------------------------------------------------------------------------------------- |
| `active` | The link has not been used and has not expired or been revoked. It can still be opened |
| `consumed` | The pharmacist has accepted or declined. The link can no longer be opened |
| `expired` | The link passed its `expires_at` time without being used |
| `revoked` | The link was revoked via the API before it was used |
### Error Responses
| Status Code | Description |
| ----------- | ------------------------------------- |
| `401` | Missing or invalid API key |
| `403` | Missing `review_link:read` permission |
## Revoke Review Link
Revoke an active review link so it can no longer be opened. Only links that are still `active` can be revoked -- revoking an already-used, expired, or revoked link returns `404`.
```bash theme={null}
DELETE /v1/management/review-links/{uid}
```
The UID of the review link to revoke
**Required permission:** `review_link:write`
### Example Request
```bash theme={null}
curl -X DELETE "https://api.rxscale.com/v1/management/review-links/rl-def456" \
-H "X-API-Key: your-api-key-here"
```
### Response
Returns `204 No Content` with an empty body on success.
### Error Responses
| Status Code | Description |
| ----------- | ------------------------------------------------------------------------ |
| `401` | Missing or invalid API key |
| `403` | Missing `review_link:write` permission |
| `404` | Review link not found for this organisation, or not in an `active` state |
## The Pharmacist's View
When a pharmacist opens the link, they do not need to log in. The page walks them through a short, guided flow:
The pharmacist opens the URL you shared. The page shows the shop's name, its icon, and the list of items in the order -- enough to identify what needs to be reviewed.
The pharmacist chooses one of two actions:
* **Accept & download** -- downloads the signed prescription PDF and marks the order as accepted.
* **Decline** -- marks the order as declined without downloading anything.
As soon as a decision is made, the link is consumed. Re-opening it shows a "no longer valid" page.
No patient-identifying information is shown on the review page itself. The full prescription -- including patient details -- is only inside the downloaded PDF, which is available exclusively through the **Accept & download** action.
If the pharmacist opens a link that has already been used, has expired, or was revoked, they see a page explaining that the link is no longer valid or has already been used, rather than the order details.
You can track how a link was used through the [List Review Links](#list-review-links) endpoint: the `status`, `decision`, `consumed_at`, and `visit_count` fields tell you whether a pharmacist opened the link and what they decided.
# Scheduling
Source: https://docs.rxscale.com/api-reference/management/scheduling
Manage appointments, appointment types, reminders, and doctor availability via the Management API
# Scheduling
Administer your organisation's scheduling configuration with an API key instead of
the admin interface. You can list and cancel scheduled appointments, manage
appointment types and their reminders, and configure each doctor's recurring
availability rules.
All endpoints are scoped to the organisation that owns the API key. UIDs that
belong to another organisation are treated as not found.
## Permissions
Scheduling endpoints are gated by two permissions:
* `scheduling:read` — required for all read (`GET`) endpoints.
* `scheduling:admin` — required for all write endpoints (`POST`, `PATCH`, `DELETE`).
A key with `scheduling:admin` is not automatically granted `scheduling:read`;
add both if you need to read and write. Contact your RxScale account manager to
adjust permissions.
All requests authenticate with the `X-API-Key` header. See
[Authentication](/authentication) for details.
## Appointments
### List Appointments
```bash theme={null}
GET /v1/management/scheduling/appointments
```
Returns a paginated list of scheduled appointments for the organisation.
**Required permission:** `scheduling:read`
Page number (0-indexed)
Number of appointments per page
Filter appointments by doctor UID
Filter appointments by patient UID
Filter by appointment status. One of `held`, `confirmed`, `cancelled`,
`expired`, `completed`, `no_show`, or `all`. When omitted, the default
server-side filtering applies.
Filter appointments starting at or after this Unix timestamp
Filter appointments starting at or before this Unix timestamp. Must be
greater than `from` when both are supplied.
#### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/scheduling/appointments?page=0&limit=25&status=confirmed" \
-H "X-API-Key: your-api-key-here"
```
#### Response
```json theme={null}
{
"data": [
{
"uid": "mtg-abc123",
"status": "confirmed",
"doctor_uid": "doc-456",
"doctor_display_name": "Dr. Schmidt",
"patient_uid": "pat-789",
"patient_display_name": "Jane Doe",
"appointment_type_uid": "apt-001",
"appointment_type_name": "Initial consultation",
"visit_reason": "Medication review before changing dosage",
"meeting_type": "CONSULTATION",
"meeting_format": "DIGITAL",
"start_date": 1712300000,
"end_date": 1712301800,
"created_at": 1712200000,
"cancelled_at": null,
"cancellation_reason": null,
"payment_status": "paid",
"payment_testmode": false
}
],
"totalRegistries": 42,
"totalPages": 2
}
```
#### Response Fields
| Field | Type | Description |
| ------------------------------ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `data[].uid` | string | Scheduled meeting UID |
| `data[].status` | string | Appointment status (`held`, `confirmed`, `cancelled`, `expired`, `completed`, `no_show`) |
| `data[].doctor_uid` | string | Doctor UID |
| `data[].doctor_display_name` | string | Doctor display name |
| `data[].patient_uid` | string | Patient UID |
| `data[].patient_display_name` | string | Patient display name |
| `data[].appointment_type_uid` | string \| null | Appointment type UID, if any |
| `data[].appointment_type_name` | string \| null | Appointment type name, if any |
| `data[].visit_reason` | string \| null | Optional reason supplied during booking. May also be shown in reminder notifications and synced calendar invites |
| `data[].meeting_type` | string | Meeting type (e.g. `CONSULTATION`, `FOLLOW_UP`, `INITIAL`, `REVIEW`, `ON_DEMAND`) |
| `data[].meeting_format` | string | Meeting format (`DIGITAL`, `IN_PERSON`) |
| `data[].start_date` | integer | Start time (Unix timestamp) |
| `data[].end_date` | integer | End time (Unix timestamp) |
| `data[].created_at` | integer | Creation timestamp (Unix) |
| `data[].cancelled_at` | integer \| null | Cancellation timestamp (Unix), if cancelled |
| `data[].cancellation_reason` | string \| null | Reason supplied at cancellation, if cancelled |
| `data[].payment_status` | string | Status of the appointment's payment. Absent entirely when the appointment carries no payment |
| `data[].payment_testmode` | boolean | Whether that payment was taken in the payment provider's test mode, in which case no money moved. Recorded when the payment was created and never re-derived, so it stays `true` after the appointment type is switched back to live payments. Absent entirely when the appointment carries no payment |
| `totalRegistries` | integer | Total number of appointments matching the filter |
| `totalPages` | integer | Total number of pages |
### Cancel an Appointment
```bash theme={null}
POST /v1/management/scheduling/appointments/{meeting_uid}/cancel
```
Cancels a scheduled appointment and records the supplied reason.
**Required permission:** `scheduling:admin`
The scheduled meeting UID
Reason for cancelling the appointment (must not be empty)
#### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/scheduling/appointments/mtg-abc123/cancel" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"reason": "Patient requested cancellation"
}'
```
#### Response
```json theme={null}
{
"appointment_uid": "mtg-abc123",
"status": "cancelled",
"cancelled_at": 1712250000,
"cancellation_reason": "Patient requested cancellation"
}
```
**Cancelling a paid appointment refunds it in full.** When the appointment carries a payment
that is `paid`, `partially_refunded` or `refund_required`, this endpoint automatically requests
a refund of everything still refundable on it. The response does not change — the refund is
requested here and completed against the payment provider moments later.
To keep part of the payment, make a partial refund from the admin interface first, then cancel.
The RxScale platform fee is not returned with a refund: your Mollie account sends the payer the
full amount and RxScale keeps its fee.
## Appointment Types
An appointment type defines a bookable kind of meeting (duration, hold behaviour,
room and rebooking strategy).
### List Appointment Types
```bash theme={null}
GET /v1/management/scheduling/appointment-types
```
**Required permission:** `scheduling:read`
#### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/scheduling/appointment-types" \
-H "X-API-Key: your-api-key-here"
```
#### Response
```json theme={null}
{
"data": [
{
"uid": "apt-001",
"organisation_uid": "org-123",
"organisation": "org-123",
"name": "Initial consultation",
"meeting_type": "CONSULTATION",
"duration_minutes": 30,
"hold_ttl_seconds": 900,
"booking_min_notice_minutes": 10,
"cancellation_min_notice_minutes": 60,
"rebooking_min_notice_minutes": 120,
"room_strategy": "persistent_per_provider",
"rebooking_mode": "same_doctor_only",
"doctor_assignment_mode": "all_available_doctors",
"allow_patient_rebooking": true,
"active": true,
"max_bookings_per_slot": 1,
"default_price_amount": 4900,
"currency": "EUR",
"default_vat_rate_bp": 1900,
"payment_hold_ttl_seconds": 1800,
"payment_mode": "live",
"created_at": 1712100000,
"updated_at": 1712100000,
"deleted_at": null
}
]
}
```
### Create an Appointment Type
```bash theme={null}
POST /v1/management/scheduling/appointment-types
```
**Required permission:** `scheduling:admin`
Display name (1–255 characters)
Meeting type. One of `CONSULTATION`, `FOLLOW_UP`, `INITIAL`, `REVIEW`,
`ON_DEMAND`.
Appointment duration in minutes (1–1440)
How long a tentative hold survives before expiring, in seconds (1–86400)
Default minimum notice required before patients can book this appointment type,
in minutes. Doctor-specific settings can override this value.
Minimum notice required to cancel, in minutes (≥ 0)
Minimum notice required to rebook, in minutes (≥ 0)
Room allocation strategy. One of `persistent_per_provider`,
`per_appointment`.
Rebooking mode. One of `same_doctor_only`,
`any_doctor_same_organisation`.
Controls which doctors can offer this appointment type. Use
`all_available_doctors` to include every doctor with availability, or
`selected_doctors_only` to require an active doctor-specific setting.
Whether patients may rebook their own appointments of this type
Whether the appointment type is bookable
#### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/scheduling/appointment-types" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"name": "Initial consultation",
"meeting_type": "CONSULTATION",
"duration_minutes": 30,
"hold_ttl_seconds": 900,
"booking_min_notice_minutes": 10,
"cancellation_min_notice_minutes": 60,
"rebooking_min_notice_minutes": 120,
"room_strategy": "persistent_per_provider",
"rebooking_mode": "same_doctor_only",
"doctor_assignment_mode": "all_available_doctors",
"allow_patient_rebooking": true,
"active": true
}'
```
Returns `201 Created` with the created appointment type (same shape as a list
entry).
### Update an Appointment Type
```bash theme={null}
PATCH /v1/management/scheduling/appointment-types/{appointment_type_uid}
```
Partial update — only the fields you send are changed. All body fields are
optional and accept the same values and ranges as on create.
**Required permission:** `scheduling:admin`
The appointment type UID
Display name (1–255 characters)
One of `CONSULTATION`, `FOLLOW_UP`, `INITIAL`, `REVIEW`, `ON_DEMAND`
Appointment duration in minutes (1–1440)
Hold lifetime in seconds (1–86400)
Default minimum booking notice in minutes (≥ 0)
Minimum cancellation notice in minutes (≥ 0)
Minimum rebooking notice in minutes (≥ 0)
One of `persistent_per_provider`, `per_appointment`
One of `same_doctor_only`, `any_doctor_same_organisation`
One of `all_available_doctors`, `selected_doctors_only`
Whether patients may rebook their own appointments of this type
Whether the appointment type is bookable
#### Example Request
```bash theme={null}
curl -X PATCH "https://api.rxscale.com/v1/management/scheduling/appointment-types/apt-001" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"duration_minutes": 45,
"active": false
}'
```
Returns `200 OK` with the updated appointment type.
### Delete an Appointment Type
```bash theme={null}
DELETE /v1/management/scheduling/appointment-types/{appointment_type_uid}
```
**Required permission:** `scheduling:admin`
The appointment type UID
#### Example Request
```bash theme={null}
curl -X DELETE "https://api.rxscale.com/v1/management/scheduling/appointment-types/apt-001" \
-H "X-API-Key: your-api-key-here"
```
Returns `204 No Content` on success.
## Reminders
Reminders are configured per appointment type and notify a recipient role a fixed
number of minutes before the appointment starts.
### List Reminders
```bash theme={null}
GET /v1/management/scheduling/appointment-types/{appointment_type_uid}/reminders
```
**Required permission:** `scheduling:read`
The appointment type UID
#### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/scheduling/appointment-types/apt-001/reminders" \
-H "X-API-Key: your-api-key-here"
```
#### Response
```json theme={null}
{
"data": [
{
"uid": "rem-001",
"appointment_type_uid": "apt-001",
"recipient_role": "patient",
"minutes_before": 1440,
"send_email": true,
"send_sms": false,
"active": true
}
]
}
```
#### Response Fields
| Field | Type | Description |
| ----------------------------- | ------- | ---------------------------------------------- |
| `data[].uid` | string | Reminder UID |
| `data[].appointment_type_uid` | string | Parent appointment type UID |
| `data[].recipient_role` | string | Who is notified (`patient`, `doctor`, `admin`) |
| `data[].minutes_before` | integer | Minutes before the appointment to send |
| `data[].send_email` | boolean | Whether to send an email |
| `data[].send_sms` | boolean | Whether to send an SMS |
| `data[].active` | boolean | Whether the reminder is active |
### Create a Reminder
```bash theme={null}
POST /v1/management/scheduling/appointment-types/{appointment_type_uid}/reminders
```
**Required permission:** `scheduling:admin`
The appointment type UID
Who is notified. One of `patient`, `doctor`, `admin`.
Minutes before the appointment to send the reminder (1–86400, i.e. up to 60
days)
Whether to send an email
Whether to send an SMS
Whether the reminder is active
#### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/scheduling/appointment-types/apt-001/reminders" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"recipient_role": "patient",
"minutes_before": 1440,
"send_email": true,
"send_sms": false,
"active": true
}'
```
Returns `201 Created` with the created reminder (same shape as a list entry).
### Update a Reminder
```bash theme={null}
PATCH /v1/management/scheduling/appointment-types/{appointment_type_uid}/reminders/{reminder_uid}
```
Partial update — only the fields you send are changed.
**Required permission:** `scheduling:admin`
The appointment type UID
The reminder UID
One of `patient`, `doctor`, `admin`
Minutes before the appointment to send the reminder (1–86400)
Whether to send an email
Whether to send an SMS
Whether the reminder is active
#### Example Request
```bash theme={null}
curl -X PATCH "https://api.rxscale.com/v1/management/scheduling/appointment-types/apt-001/reminders/rem-001" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"minutes_before": 60,
"send_sms": true
}'
```
Returns `200 OK` with the updated reminder.
### Delete a Reminder
```bash theme={null}
DELETE /v1/management/scheduling/appointment-types/{appointment_type_uid}/reminders/{reminder_uid}
```
**Required permission:** `scheduling:admin`
The appointment type UID
The reminder UID
#### Example Request
```bash theme={null}
curl -X DELETE "https://api.rxscale.com/v1/management/scheduling/appointment-types/apt-001/reminders/rem-001" \
-H "X-API-Key: your-api-key-here"
```
Returns `204 No Content` on success.
## Availability Rules
Availability rules define a doctor's recurring weekly bookable windows. Times are
expressed as minutes from midnight (for example, `540` is 09:00 and `1020` is
17:00).
### List Availability Rules
```bash theme={null}
GET /v1/management/scheduling/doctors/{doctor_uid}/availability-rules
```
**Required permission:** `scheduling:read`
The doctor UID
#### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/scheduling/doctors/doc-456/availability-rules" \
-H "X-API-Key: your-api-key-here"
```
#### Response
```json theme={null}
{
"data": [
{
"uid": "rule-001",
"doctor_uid": "doc-456",
"weekday": 0,
"start_time": 540,
"end_time": 1020,
"buffer_minutes": 10,
"valid_from": null,
"valid_until": null,
"active": true,
"appointment_type_uid": null
}
]
}
```
#### Response Fields
| Field | Type | Description |
| ----------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `data[].uid` | string | Availability rule UID |
| `data[].doctor_uid` | string | Doctor UID |
| `data[].weekday` | integer | Day of week (0 = Monday … 6 = Sunday) |
| `data[].start_time` | integer | Start of the window, in minutes from midnight (0–1440) |
| `data[].end_time` | integer | End of the window, in minutes from midnight (0–1440) |
| `data[].buffer_minutes` | integer | Buffer between appointments, in minutes |
| `data[].valid_from` | integer \| null | Optional start of validity (Unix timestamp) |
| `data[].valid_until` | integer \| null | Optional end of validity (Unix timestamp) |
| `data[].active` | boolean | Whether the rule is active |
| `data[].appointment_type_uid` | string \| null | Appointment type this rule is scoped to, or `null` for all types. See [Scoping availability to a single appointment type](#scoping-availability-to-a-single-appointment-type). |
### Create an Availability Rule
```bash theme={null}
POST /v1/management/scheduling/doctors/{doctor_uid}/availability-rules
```
**Required permission:** `scheduling:admin`
The doctor UID
Day of week (0 = Monday … 6 = Sunday)
Start of the window, in minutes from midnight (0–1440)
End of the window, in minutes from midnight (0–1440)
Buffer between appointments, in minutes (≥ 0)
Optional start of validity (Unix timestamp)
Optional end of validity (Unix timestamp)
Whether the rule is active
Optional. Omit or set to `null` to apply the rule to all appointment types.
Set it to scope the rule to a single appointment type. See [Scoping
availability to a single appointment
type](#scoping-availability-to-a-single-appointment-type).
#### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/scheduling/doctors/doc-456/availability-rules" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"weekday": 0,
"start_time": 540,
"end_time": 1020,
"buffer_minutes": 10,
"active": true
}'
```
Returns `201 Created` with the created availability rule (same shape as a list
entry).
### Update an Availability Rule
```bash theme={null}
PATCH /v1/management/scheduling/doctors/{doctor_uid}/availability-rules/{rule_uid}
```
Partial update — only the fields you send are changed. To remove an existing
validity bound, send the corresponding `clear_*` flag rather than a value.
**Required permission:** `scheduling:admin`
The doctor UID
The availability rule UID
Day of week (0 = Monday … 6 = Sunday)
Start of the window, in minutes from midnight (0–1440)
End of the window, in minutes from midnight (0–1440)
Buffer between appointments, in minutes (≥ 0)
Set the start of validity (Unix timestamp)
Set the end of validity (Unix timestamp)
Whether the rule is active
Clear the existing `valid_from` bound (set it to null)
Clear the existing `valid_until` bound (set it to null)
Scope the rule to a single appointment type. See [Scoping availability to a
single appointment type](#scoping-availability-to-a-single-appointment-type).
Clear the existing appointment-type scope so the rule applies to all types again
#### Example Request
```bash theme={null}
curl -X PATCH "https://api.rxscale.com/v1/management/scheduling/doctors/doc-456/availability-rules/rule-001" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"end_time": 960,
"clear_valid_until": true
}'
```
Returns `200 OK` with the updated availability rule.
### Delete an Availability Rule
```bash theme={null}
DELETE /v1/management/scheduling/doctors/{doctor_uid}/availability-rules/{rule_uid}
```
**Required permission:** `scheduling:admin`
The doctor UID
The availability rule UID
#### Example Request
```bash theme={null}
curl -X DELETE "https://api.rxscale.com/v1/management/scheduling/doctors/doc-456/availability-rules/rule-001" \
-H "X-API-Key: your-api-key-here"
```
Returns `204 No Content` on success.
## Availability Date Overrides
Date overrides set a doctor's availability for a **specific calendar date**,
replacing their recurring weekly rules on that date. Each override is either a
**time window** (custom hours that day) or a **day off** (no bookable slots that
day). On any date that has at least one override, the doctor's recurring rules
are suppressed and only the overrides apply.
* `date` is the **UTC-midnight Unix timestamp** of the calendar date — a multiple
of `86400` (for example, `1749427200` is 2025-06-09 00:00:00 UTC).
* `start_time` / `end_time` are minutes from midnight (for example, `480` is
08:00 and `720` is 12:00). For a day off, both are `null`.
### List Date Overrides
```bash theme={null}
GET /v1/management/scheduling/doctors/{doctor_uid}/availability-date-overrides
```
**Required permission:** `scheduling:read`
The doctor UID
Only return overrides on or after this UTC-midnight epoch
Only return overrides on or before this UTC-midnight epoch
Zero-indexed page number
Page size (maximum 200)
#### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/scheduling/doctors/doc-456/availability-date-overrides?from=1749427200&limit=50" \
-H "X-API-Key: your-api-key-here"
```
#### Response
The list is paginated, and a single request may span **at most \~6 months**. Omit
both `from` and `to` for "today onward"; pass one to anchor the window, or both to
choose a range — a span wider than \~6 months is trimmed from the `to` end.
`totalRegistries` is the number of overrides matching the (clamped) window and
`totalPages` is `ceil(totalRegistries / limit)`.
```json theme={null}
{
"data": [
{
"uid": "ovr-001",
"doctor_uid": "doc-456",
"date": 1749427200,
"start_time": 480,
"end_time": 720,
"buffer_minutes": 0,
"appointment_type_uid": null
},
{
"uid": "ovr-002",
"doctor_uid": "doc-456",
"date": 1749513600,
"start_time": null,
"end_time": null,
"buffer_minutes": 0,
"appointment_type_uid": null
}
],
"totalRegistries": 2,
"totalPages": 1
}
```
#### Response Fields
| Field | Type | Description |
| ----------------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data[].uid` | string | Date override UID |
| `data[].doctor_uid` | string | Doctor UID |
| `data[].date` | integer | UTC-midnight epoch of the calendar date (a multiple of 86400) |
| `data[].start_time` | integer \| null | Start of the window, in minutes from midnight (0–1440); `null` for a day off |
| `data[].end_time` | integer \| null | End of the window, in minutes from midnight (0–1440); `null` for a day off |
| `data[].buffer_minutes` | integer | Buffer between appointments, in minutes |
| `data[].appointment_type_uid` | string \| null | Appointment type this override is scoped to, or `null` for all types. See [Scoping availability to a single appointment type](#scoping-availability-to-a-single-appointment-type). |
### Create a Date Override
```bash theme={null}
POST /v1/management/scheduling/doctors/{doctor_uid}/availability-date-overrides
```
**Required permission:** `scheduling:admin`
The doctor UID
UTC-midnight epoch of the calendar date (a multiple of 86400)
If true, the date is fully unavailable — omit `start_time`/`end_time`
Start of the window, in minutes from midnight (0–1440). Required unless `day_off` is true
End of the window, in minutes from midnight (0–1440). Required unless `day_off` is true
Buffer between appointments, in minutes (≥ 0)
Optional. Omit or set to `null` for an override that applies to all appointment
types. Set it to scope the override (including a day off) to a single
appointment type. See [Scoping availability to a single appointment
type](#scoping-availability-to-a-single-appointment-type).
#### Example Request — custom hours
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/scheduling/doctors/doc-456/availability-date-overrides" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"date": 1749427200,
"start_time": 480,
"end_time": 720
}'
```
#### Example Request — day off
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/scheduling/doctors/doc-456/availability-date-overrides" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"date": 1749513600,
"day_off": true
}'
```
Returns `201 Created` with the created date override (same shape as a list
entry). Adding a day off clears any existing windows on that date, and adding a
window clears an existing day-off marker.
### Update a Date Override
```bash theme={null}
PATCH /v1/management/scheduling/doctors/{doctor_uid}/availability-date-overrides/{override_uid}
```
Partial update — only the fields you send are changed. Send `day_off: true` to
turn the date into a day off (clearing its window).
**Required permission:** `scheduling:admin`
The doctor UID
The date override UID
UTC-midnight epoch of the calendar date (a multiple of 86400)
If true, clear the date's window and mark it a day off
Start of the window, in minutes from midnight (0–1440)
End of the window, in minutes from midnight (0–1440)
Buffer between appointments, in minutes (≥ 0)
Scope the override to a single appointment type. See [Scoping availability to a
single appointment type](#scoping-availability-to-a-single-appointment-type).
Clear the existing appointment-type scope so the override applies to all types again
#### Example Request
```bash theme={null}
curl -X PATCH "https://api.rxscale.com/v1/management/scheduling/doctors/doc-456/availability-date-overrides/ovr-001" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"start_time": 540,
"end_time": 660
}'
```
Returns `200 OK` with the updated date override.
### Delete a Date Override
```bash theme={null}
DELETE /v1/management/scheduling/doctors/{doctor_uid}/availability-date-overrides/{override_uid}
```
**Required permission:** `scheduling:admin`
The doctor UID
The date override UID
#### Example Request
```bash theme={null}
curl -X DELETE "https://api.rxscale.com/v1/management/scheduling/doctors/doc-456/availability-date-overrides/ovr-001" \
-H "X-API-Key: your-api-key-here"
```
Returns `204 No Content` on success. The date returns to the doctor's recurring
availability.
## Scoping availability to a single appointment type
Both availability rules and date overrides accept an optional
`appointment_type_uid`. It lets a doctor offer different hours for different kinds
of appointment — for example, video follow-ups only in the afternoon while
in-person consultations keep the doctor's general morning hours.
* **`null` (the default) means all appointment types.** When you omit
`appointment_type_uid` or send `null`, the rule or override behaves exactly as
before and applies to every appointment type. Existing rules and overrides are
unaffected.
* **A scoped rule replaces the doctor's general hours for that type, per
weekday.** On a weekday where the doctor has at least one rule scoped to a given
type, that type uses only the scoped rules for that weekday; the general
(all-types) rules are ignored for that type on that weekday. Weekdays with no
scoped rule for the type fall back to the doctor's general hours.
* **A scoped override replaces the doctor's general override for that type, per
date.** On a date where the doctor has an override scoped to a given type, that
type uses only the scoped override for that date; on dates with no scoped
override the type falls back to the general override (or, if there is none, to
the recurring rules).
* **A general day off suppresses every type** — unless that type has its own
override for the date. To keep one appointment type bookable on an otherwise
closed day, add a date override scoped to that type on the same date.
* The appointment type must belong to the same organisation as the doctor;
otherwise the request is treated as not found.
The appointment type's UID is returned as `appointment_type_uid` on every rule and
override (`null` when not scoped).
#### Example Request — afternoon hours for one appointment type
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/scheduling/doctors/doc-456/availability-rules" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"weekday": 2,
"start_time": 840,
"end_time": 960,
"appointment_type_uid": "apt-001"
}'
```
#### Response
```json theme={null}
{
"uid": "rule-001",
"doctor_uid": "doc-456",
"weekday": 2,
"start_time": 840,
"end_time": 960,
"buffer_minutes": 0,
"valid_from": null,
"valid_until": null,
"active": true,
"appointment_type_uid": "apt-001"
}
```
#### Reverting a row to all appointment types
On either `PATCH` endpoint, send `clear_appointment_type: true` to remove the
scope so the rule or override applies to all appointment types again:
```bash theme={null}
curl -X PATCH "https://api.rxscale.com/v1/management/scheduling/doctors/doc-456/availability-rules/rule-001" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"clear_appointment_type": true
}'
```
# Waiting Room
Source: https://docs.rxscale.com/api-reference/management/waiting-room
Manage waiting room registrations via the Management API
# Waiting Room
Register patients for the virtual waiting room, check their queue status, and cancel registrations.
## Register Patient
Register a patient for the waiting room in a specific shop.
```bash theme={null}
POST /v1/management/waiting-room/register
```
**Required permission:** `waiting_room:write`
### Request Body
```json theme={null}
{
"shop_uid": "shop-abc123",
"patient_profile_uid": "pp-xyz789",
"preferred_doctor_uid": "doc-456",
"visit_reason": "Follow-up consultation"
}
```
| Field | Type | Required | Description |
| ---------------------- | ------ | -------- | --------------------------------------------------------------------------- |
| `shop_uid` | string | Yes | Shop UID where the patient is registering |
| `patient_profile_uid` | string | Yes | Patient profile UID |
| `preferred_doctor_uid` | string | No | Preferred doctor UID. Must be a doctor in the same organisation as the shop |
| `visit_reason` | string | No | Reason for the visit |
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/waiting-room/register" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"shop_uid": "shop-abc123",
"patient_profile_uid": "pp-xyz789",
"preferred_doctor_uid": "doc-456",
"visit_reason": "Follow-up consultation"
}'
```
### Response (201 Created)
```json theme={null}
{
"queue_uid": "q-abc123",
"position": 3,
"estimated_wait_minutes": 15
}
```
### Errors
| Status | When |
| ------ | ------------------------------------------------------------------------------------------ |
| `400` | The request body is invalid |
| `404` | The shop, the patient profile, or the preferred doctor does not exist in your organisation |
A `preferred_doctor_uid` that belongs to another organisation is reported as `404`,
identically to an unknown UID.
## Check Queue Status
Get the current status of a queue entry, including position and estimated wait time.
```bash theme={null}
GET /v1/management/waiting-room/{queue_uid}/status
```
The queue entry UID
**Required permission:** `waiting_room:read`
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/waiting-room/q-abc123/status" \
-H "X-API-Key: your-api-key-here"
```
### Response
```json theme={null}
{
"queue_uid": "q-abc123",
"status": "waiting",
"position": 2,
"estimated_wait_minutes": 10,
"video_room_id": null,
"allocated_at": null
}
```
### Status Values
| Status | Description |
| ----------- | -------------------------------------- |
| `waiting` | Patient is waiting in the queue |
| `allocated` | Patient has been allocated to a doctor |
| `completed` | Consultation has been completed |
| `cancelled` | Registration was cancelled |
When a patient is allocated, `video_room_id` and `allocated_at` will be populated.
## Cancel Registration
Cancel a queue registration.
```bash theme={null}
DELETE /v1/management/waiting-room/{queue_uid}
```
The queue entry UID to cancel
**Required permission:** `waiting_room:write`
### Example Request
```bash theme={null}
curl -X DELETE "https://api.rxscale.com/v1/management/waiting-room/q-abc123" \
-H "X-API-Key: your-api-key-here"
```
### Response
```json theme={null}
{
"message": "Registration cancelled"
}
```
# Wallet Passes
Source: https://docs.rxscale.com/api-reference/management/wallet-passes
Manage wallet pass templates and send push notifications via the Management API
# Wallet Passes
Query wallet pass templates, list wallet passes for customers, verify a scanned pass, and send push notifications to wallet pass holders.
## List Templates
Retrieve wallet pass templates for a specific shop.
```bash theme={null}
GET /v1/management/wallet-passes/templates
```
The shop identifier
**Required permission:** `wallet_pass_template:read`
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/wallet-passes/templates?shop_identifier=my-shop" \
-H "X-API-Key: your-api-key-here"
```
### Response
```json theme={null}
{
"data": [
{
"uid": "wpt-abc123",
"display_name": "Loyalty Card",
"pass_type": "generic",
"shop_identifier": "my-shop"
}
],
"total": 1
}
```
## List Wallet Passes
Retrieve wallet passes for a specific shop customer and shop combination.
```bash theme={null}
GET /v1/management/wallet-passes
```
The shop customer ID
The shop identifier
Optional template UID to filter by
**Required permission:** `wallet_pass:read`
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/wallet-passes?shop_customer_id=cust-123&shop_identifier=my-shop" \
-H "X-API-Key: your-api-key-here"
```
### Response
```json theme={null}
{
"data": [
{
"uid": "wp-abc123",
"wallet_pass_template_uid": "wpt-abc123",
"shop_customer_id": "cust-123",
"status": "active"
}
],
"total": 1
}
```
## Verify a Scanned Pass
Resolve a scanned Patient Pass to shop-scoped identity handles. Requires an API key with the `wallet_pass:verify` permission.
```bash theme={null}
POST /v1/management/wallet-passes/verify
```
**Required permission:** `wallet_pass:verify`
### Request Body
The value encoded in the pass barcode. Treat it as a credential and send it in the body, never in a URL.
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/wallet-passes/verify" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{"wallet_pass_uid": "3f9a1c2e-7b04-4c11-9f3d-2a8e5b6c0d17"}'
```
### Response (200)
```json theme={null}
{
"valid": true,
"status": "active",
"wallet_pass_uid": "3f9a1c2e-7b04-4c11-9f3d-2a8e5b6c0d17",
"patient_profile_uid": "b21d4f8a-5c33-4e90-8a72-1f6b9d0c4e55",
"shop_customer_id": "cust-123"
}
```
A revoked pass returns `"valid": false` with `"status": "revoked"`. `shop_customer_id` is absent for passes issued before the shop customer requirement.
Treat the `wallet_pass_uid` as a credential -- anyone holding it can resolve the patient behind the pass. Send it in the request body, never in a URL, and do not write it to logs.
This endpoint returns identity handles only. Load the patient's details with `GET /v1/management/patients`.
### Error Responses
| Status Code | Description |
| ----------- | --------------------------------------------------------- |
| `400` | Missing `wallet_pass_uid` |
| `403` | Missing `wallet_pass:verify` permission |
| `404` | Unknown pass, or a pass belonging to another organisation |
## Send Push Notifications
Send push notifications to one or more wallet pass holders. Accepts a batch of notifications.
```bash theme={null}
POST /v1/management/wallet-passes/push-notifications
```
**Required permission:** `wallet_pass_push_notification:write`
### Request Body
The request body is a JSON array of notification objects.
```json theme={null}
[
{
"wallet_pass_uid": "wp-abc123",
"message": "Your prescription is ready for pickup!"
},
{
"wallet_pass_uid": "wp-def456",
"message": "Hello {first_name}, your order has been shipped."
}
]
```
| Field | Type | Required | Description |
| ----------------- | ------ | -------- | ---------------------------------------------------------------- |
| `wallet_pass_uid` | string | Yes | UID of the wallet pass to send the notification to |
| `message` | string | Yes | Push notification message text (supports `{field}` placeholders) |
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/wallet-passes/push-notifications" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '[
{
"wallet_pass_uid": "wp-abc123",
"message": "Your prescription is ready for pickup!"
}
]'
```
### Response (201 Created)
```json theme={null}
{
"data": [
{
"uid": "wpn-abc123",
"wallet_pass_uid": "wp-abc123",
"message": "Your prescription is ready for pickup!",
"status": "queued"
}
],
"total": 1
}
```
### Error Responses
| Status Code | Description |
| ----------- | ---------------------------------------------------------------------------- |
| `400` | Invalid body format, empty array, validation error, or wallet pass not found |
| `403` | Missing `wallet_pass_push_notification:write` permission |
# Webhooks
Source: https://docs.rxscale.com/api-reference/management/webhooks
Register and manage organisation webhook subscriptions via the Management API
# Webhooks (Management API)
Register webhook subscriptions to receive real-time notifications for events across your organisation. For webhook payload formats and security, see the [Webhooks](/webhooks/overview) section.
These endpoints require the `notification_subscription` permissions: `notification_subscription:read` to list subscriptions, and `notification_subscription:write` to create, remove, or send a test webhook. A key without the required permission receives `403 Permission Denied`. See [Permissions](/permissions) for details.
## List Webhook Subscriptions
Retrieve all active webhook subscriptions for your organisation.
```bash theme={null}
GET /v1/management/notification-subscriptions
```
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/notification-subscriptions/" \
-H "X-API-Key: your-api-key-here"
```
### Response
```json theme={null}
[
{
"uid": "ns-abc123",
"notification_type": "pharmacy_order_created",
"target": "https://your-system.com/webhooks/rxscale",
"options": "WEBHOOK",
"payload_version": "1"
}
]
```
## Register Webhook Subscription
Create a new webhook subscription for your organisation.
```bash theme={null}
POST /v1/management/notification-subscriptions
```
### Request Body
```json theme={null}
{
"notification_type": "pharmacy_order_created",
"target": "https://your-system.com/webhooks/rxscale",
"payload_version": "1"
}
```
| Field | Type | Required | Description |
| ------------------- | ------ | -------- | ---------------------------------------------------- |
| `notification_type` | string | Yes | Event type to subscribe to |
| `target` | string | Yes | Webhook URL to receive notifications (must be HTTPS) |
| `meta_data` | object | No | Optional metadata for the subscription |
| `payload_version` | string | No | Payload schema version (default: `"1"`) |
### Available Event Types
| Event Type | Description |
| -------------------------------- | --------------------------------------------------------------------------------------- |
| `pharmacy_order_created` | A new pharmacy order has been created |
| `pharmacy_order_updated` | An existing order changed, including status updates and shipments added by the pharmacy |
| `pharmacy_sku_stock_updated` | Stock level changed for a pharmacy SKU |
| `patient_doctor_meeting_updated` | A patient-doctor meeting changed lifecycle state |
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/notification-subscriptions/" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"notification_type": "pharmacy_order_created",
"target": "https://your-system.com/webhooks/rxscale"
}'
```
### Response (201 Created)
```json theme={null}
{
"uid": "ns-abc123",
"notification_type": "pharmacy_order_created",
"target": "https://your-system.com/webhooks/rxscale",
"options": "WEBHOOK",
"payload_version": "1"
}
```
## Remove Webhook Subscription
Delete (deactivate) a webhook subscription by specifying the target URL and notification type.
```bash theme={null}
DELETE /v1/management/notification-subscriptions
```
The webhook target URL
The notification type to unsubscribe from
### Example Request
```bash theme={null}
curl -X DELETE "https://api.rxscale.com/v1/management/notification-subscriptions/?target=https://your-system.com/webhooks/rxscale¬ification_type=pharmacy_order_created" \
-H "X-API-Key: your-api-key-here"
```
Returns `204 No Content` on success.
## Test Webhook Delivery
Send a sample webhook payload to a target URL to verify your endpoint is configured correctly. This is useful for verifying your webhook endpoint is working before creating a subscription.
```bash theme={null}
POST /v1/management/notification-subscriptions/test
```
### Request Body
```json theme={null}
{
"target_url": "https://your-system.com/webhooks/rxscale",
"event_type": "pharmacy_order_created",
"header_key": "X-Custom-Auth",
"header_value": "my-secret-token"
}
```
| Field | Type | Required | Description |
| -------------- | ------ | -------- | ------------------------------------------------------------- |
| `target_url` | string | Yes | URL to send the test webhook to (must be HTTPS) |
| `event_type` | string | No | Type of event to simulate (default: `pharmacy_order_created`) |
| `header_key` | string | No | Custom header name to include in the test request |
| `header_value` | string | No | Custom header value to include in the test request |
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/notification-subscriptions/test" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"target_url": "https://your-system.com/webhooks/rxscale",
"event_type": "pharmacy_order_created",
"header_key": "X-Custom-Auth",
"header_value": "my-secret-token"
}'
```
### Response
```json theme={null}
{
"success": true,
"status_code": 200,
"payload_sent": {
"event_type": "pharmacy_order_created",
"timestamp": 1711700000,
"payload_version": "1",
"data": { ... }
}
}
```
Test webhook payloads include the header `X-Webhook-Test: true` so your endpoint can distinguish test deliveries from real ones.
# Anamnesis
Source: https://docs.rxscale.com/api-reference/public/anamnesis
Submit external anamnesis data and connect it to patients
# Anamnesis
The Anamnesis API allows external providers to submit medical questionnaire responses (anamnesis data) and connect them to patients in the RxScale system.
## Base Path
```
/api/v3-1/anamnesis
```
## Authentication
All endpoints require an API key via the `X-API-Key` header. See [Authentication](/authentication) for details.
The legacy `X-RxScale-Authorization` header is also supported for backward compatibility but `X-API-Key` is recommended for new integrations.
## Submit External Anamnesis
Submit anamnesis data from an external provider for a specific questionnaire.
```bash theme={null}
POST /api/v3-1/anamnesis/questionnaires/{questionnaire_uid}/external/submissions
```
The UID of the questionnaire to submit against
### Request Body
```json theme={null}
{
"data": {
"lastName": "Müller",
"dob": "1990-05-15"
},
"anamnesis_provider_uid": "fc9ef771-79a4-4f60-a930-d62a3527565e",
"external_identifier": "ext-submission-001"
}
```
| Field | Type | Required | Description |
| ------------------------ | ------ | -------- | ------------------------------------------------------------------------------ |
| `data` | object | Yes | The anamnesis response data. Structure depends on the questionnaire definition |
| `anamnesis_provider_uid` | string | Yes | Your anamnesis provider UID (provided by RxScale) |
| `external_identifier` | string | Yes | Your external identifier for this submission (for tracking) |
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/api/v3-1/anamnesis/questionnaires/abc123-def456/external/submissions" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"data": {"lastName": "Müller", "dob": "1990-05-15"},
"anamnesis_provider_uid": "fc9ef771-79a4-4f60-a930-d62a3527565e",
"external_identifier": "ext-submission-001"
}'
```
The `data` object structure must match the fields defined in the questionnaire. Contact your RxScale account manager to get the questionnaire schema.
## Connect Anamnesis to Patient
Link a submitted anamnesis to a patient profile using their shop customer ID.
```bash theme={null}
POST /api/v3-1/anamnesis/{anamnesis_uid}/patient
```
The UID of the anamnesis submission to connect
### Request Body
```json theme={null}
{
"shop_identifier": "my-store.myshopify.com",
"shop_patient_id": "CUST123"
}
```
| Field | Type | Required | Description |
| ----------------- | ------ | -------- | ------------------------------------- |
| `shop_identifier` | string | Yes | Your shop identifier |
| `shop_patient_id` | string | Yes | The customer ID from your shop system |
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/api/v3-1/anamnesis/xyz789/patient" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"shop_identifier": "my-store.myshopify.com",
"shop_patient_id": "CUST123"
}'
```
Each anamnesis submission can only be connected to one patient. Attempting to connect an already-connected anamnesis will result in an error.
## Typical Integration Flow
Your external system collects patient questionnaire responses and submits them via the external submissions endpoint.
The API returns an anamnesis UID that you store alongside the submission in your system.
When the patient is identified in your shop (e.g., during checkout), connect the anamnesis to their patient profile.
The connected anamnesis becomes available for doctor review in the RxScale doctor portal.
# Anamnesis (v4)
Source: https://docs.rxscale.com/api-reference/public/anamnesis-v4
Read questionnaire models and submit anamnesis responses
# Anamnesis (v4)
The v4 Anamnesis API lets you fetch a questionnaire model, render it on your own surface, and submit the patient's answers back to RxScale. Every submission is validated against the questionnaire model before it is stored, so an invalid payload never creates a record.
This is the v4 Anamnesis API. The legacy `/api/v3-1/anamnesis` endpoints are still available — see [Anamnesis](/api-reference/public/anamnesis) for that reference.
## Base Path
```
/v4/anamnesis
```
## Authentication
The read endpoints (questionnaire model, public files) and the standard `POST /submissions` endpoint are **public** and require **no API key**. They are intended to be called directly from storefronts and other client surfaces that render RxScale questionnaires.
The **external** submission endpoint (`POST /external/submissions`) is **authenticated** and requires an API key passed in the `X-API-Key` header, with the `anamnesis:external_submit` permission. You may only submit on behalf of an external anamnesis provider that belongs to your organisation — the `provider_identifier` you pass is resolved to a provider owned by the API key's organisation.
For the public endpoints, because no credentials are sent, only non-sensitive read operations (questionnaire model, public files) and write operations that are validated against the questionnaire model are exposed. Submitted answers are transmitted over HTTPS, and an encrypted copy of each submission is retained at rest.
## Endpoint Summary
| Method | Endpoint | Auth | Description |
| ------ | --------------------------------------------------------- | ----------- | ----------------------------------------------------- |
| `GET` | `/questionnaires/{questionnaire_id}` | Public | Fetch the questionnaire model and rendering metadata |
| `GET` | `/questionnaires/{questionnaire_id}/files/{filename}` | Public | Download a file referenced by the questionnaire |
| `GET` | `/questionnaires/{questionnaire_id}/products` | Public | List the products attached to the questionnaire |
| `POST` | `/questionnaires/{questionnaire_id}/submissions` | Public | Submit an anamnesis for a questionnaire |
| `POST` | `/questionnaires/{questionnaire_id}/external/submissions` | `X-API-Key` | Submit an anamnesis on behalf of an external provider |
## Get Questionnaire
Fetch the questionnaire model and the metadata you need to render it. This endpoint is **public** and requires no API key.
```bash theme={null}
GET /v4/anamnesis/questionnaires/{questionnaire_id}
```
The UID of the questionnaire
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v4/anamnesis/questionnaires/abc123-def456"
```
### Response
```json theme={null}
{
"model": {
"title": "Patient Intake",
"pages": [
{
"name": "page1",
"elements": [
{ "type": "text", "name": "lastName", "title": "Last name", "isRequired": true },
{ "type": "text", "name": "dob", "title": "Date of birth", "inputType": "date" }
]
}
]
},
"theme": {
"themeName": "default",
"colorPalette": "light"
},
"type": "direct_to_cart",
"identifier": "patient-intake",
"version": 3
}
```
| Field | Type | Description |
| ------------ | ------- | ------------------------------------------------------------------------------ |
| `model` | object | The SurveyJS questionnaire model. Pass this to the SurveyJS renderer |
| `theme` | object | The SurveyJS theme used for rendering |
| `type` | string | The questionnaire type (for example `direct_to_cart` or `product_recommender`) |
| `identifier` | string | A stable human-readable identifier for the questionnaire |
| `version` | integer | The published version of the questionnaire |
Returned when no questionnaire exists for the given `questionnaire_id`.
## Download Questionnaire File
Download a file referenced by the questionnaire (for example an image or an information PDF). The file is returned as an attachment with its original filename. This endpoint is **public** and requires no API key.
```bash theme={null}
GET /v4/anamnesis/questionnaires/{questionnaire_id}/files/{filename}
```
The UID of the questionnaire
The name of the file to download
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v4/anamnesis/questionnaires/abc123-def456/files/info.pdf" \
-o info.pdf
```
The response is the raw file bytes, served as an attachment (`Content-Disposition: attachment; filename="info.pdf"`).
Returned when the questionnaire or the requested file does not exist.
## List Questionnaire Products
List the products attached to a questionnaire. This endpoint is **public** and requires no API key, because a SurveyJS product dropdown fetches it directly from the browser while the questionnaire is being answered.
```bash theme={null}
GET /v4/anamnesis/questionnaires/{questionnaire_id}/products
```
The UID of the questionnaire
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v4/anamnesis/questionnaires/abc123-def456/products"
```
### Response
```json theme={null}
[
{ "uid": "6f1c9d20-5b7e-4a11-9f2c-1d3e4a5b6c7d", "display_name": "Ibuprofen 400mg" },
{ "uid": "b28e7f31-2c4a-4d88-a0e5-7c9b1f2a3d4e", "display_name": "Paracetamol 500mg" }
]
```
| Field | Type | Description |
| -------------- | ------ | ------------------------------------------------------------------------------------- |
| `uid` | string | The product UID. This is the value a product dropdown stores in the submitted answers |
| `display_name` | string | The product name shown to the patient |
The array is flat and free of duplicates, and is ordered by the product's short name so the choices stay stable between requests. It is shaped for a SurveyJS `choicesByUrl` binding with `valueName: "uid"` and `titleName: "display_name"`.
Returned when no questionnaire exists for the given `questionnaire_id`.
## Submit Anamnesis
Submit the patient's answers for a questionnaire. This endpoint is **public** and requires no API key. The `data` payload is validated against the questionnaire model before anything is stored. On success the submission is persisted (with an encrypted copy of the answers retained at rest) and its UID is returned.
```bash theme={null}
POST /v4/anamnesis/questionnaires/{questionnaire_id}/submissions
```
The UID of the questionnaire being answered
### Request Body
```json theme={null}
{
"data": {
"lastName": "Müller",
"dob": "1990-05-15"
}
}
```
| Field | Type | Required | Description |
| ------ | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `data` | object | Yes | The SurveyJS answer payload. Its structure must match the questionnaire model returned by [Get Questionnaire](#get-questionnaire) |
### File and signature answers
Answers to `file` and `signaturepad` questions are sent as base64 data URLs, exactly as SurveyJS produces them:
```json theme={null}
{
"data": {
"idDocument": [
{
"name": "id.png",
"type": "image/png",
"content": "data:image/png;base64,iVBORw0KGgo..."
}
]
}
}
```
The uploaded bytes are stored as-is. The media type recorded for the file is **not** taken from your `type` field verbatim — it is determined from the file's actual content, and your `type` is only consulted as a fallback when the content cannot be identified. Either way the result must be one of:
`application/pdf`, `image/gif`, `image/heic`, `image/heif`, `image/jpeg`, `image/png`, `image/tiff`, `image/webp`
Anything else is recorded as `application/octet-stream`. Such a file is still stored and still reaches the doctor, but it is offered for download instead of being previewed inline. Send one of the supported types if you need the file to render in the RxScale doctor tools.
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v4/anamnesis/questionnaires/abc123-def456/submissions" \
-H "Content-Type: application/json" \
-d '{
"data": {"lastName": "Müller", "dob": "1990-05-15"}
}'
```
### Response
```json theme={null}
{
"uid": "anam-9f8e7d6c"
}
```
| Field | Type | Description |
| ----- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `uid` | string | The UID of the created anamnesis. Use it to link the submission to a Shopify order — see the [Questionnaire Integration guide](/guides/questionnaire-integration) |
The endpoint responds with `201 Created` on success.
## Submit External Anamnesis
Submit a questionnaire response on behalf of an external anamnesis provider. The `data` payload is validated against the questionnaire model in exactly the same way as a regular submission.
This endpoint is **authenticated**: send your API key in the `X-API-Key` header. The key must have the `anamnesis:external_submit` permission. Instead of a provider UID, you pass a `provider_identifier`, which is resolved to an external anamnesis provider owned by the API key's organisation — you may only submit for a provider that belongs to your organisation.
```bash theme={null}
POST /v4/anamnesis/questionnaires/{questionnaire_id}/external/submissions
```
The UID of the questionnaire being answered
### Request Body
```json theme={null}
{
"provider_identifier": "my-clinic-provider",
"external_identifier": "ext-submission-001",
"data": {
"lastName": "Müller",
"dob": "1990-05-15"
}
}
```
| Field | Type | Required | Description |
| --------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `provider_identifier` | string | Yes | The identifier of your external anamnesis provider (provided by RxScale). This is an identifier, not a UID, and the provider must belong to the organisation that owns the API key |
| `external_identifier` | string | Yes | Your own identifier for this submission, used for tracking and for linking to orders |
| `data` | object | Yes | The SurveyJS answer payload. Its structure must match the questionnaire model |
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v4/anamnesis/questionnaires/abc123-def456/external/submissions" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{
"provider_identifier": "my-clinic-provider",
"external_identifier": "ext-submission-001",
"data": {"lastName": "Müller", "dob": "1990-05-15"}
}'
```
### Response
```json theme={null}
{
"uid": "ext-anam-1a2b3c4d"
}
```
| Field | Type | Description |
| ----- | ------ | ---------------------------------------------------- |
| `uid` | string | The UID of the created external anamnesis submission |
The endpoint responds with `201 Created` on success.
## Submission Validation
Both submission endpoints validate the `data` payload against the questionnaire model **before** writing anything to the database. If validation fails, the request is rejected with `400 Bad Request` and **no record is created**.
The error body contains the list of validation problems reported against the questionnaire model:
```json theme={null}
{
"error": [
"lastName is required",
"dob must be a valid date"
]
}
```
If the request body itself is malformed — for example a missing required field such as `data` or `provider_identifier` — the `400` response instead reports the offending field:
```json theme={null}
{
"error": {
"data": ["Missing data for required field."]
}
}
```
If the submission validator is temporarily unreachable, the request fails with `502 Bad Gateway` and **no record is created**. This is a transient upstream failure, not a problem with your payload — retry the request:
```json theme={null}
{
"error": "submission validator unavailable"
}
```
Always fetch the latest questionnaire model with [Get Questionnaire](#get-questionnaire) and render your form from it. Submitting answers that do not match the current model will fail validation and the submission will not be stored.
### Error Responses
| Status | Meaning |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | The submission failed questionnaire model validation, or the request body was malformed. No record is created |
| `401` | Missing or invalid API key (external submission endpoint only) |
| `403` | The API key lacks the `anamnesis:external_submit` permission (external submission endpoint only) |
| `404` | No questionnaire exists for the given `questionnaire_id`, or the `provider_identifier` does not match a provider owned by your organisation |
| `409` | An external submission with this `external_identifier` already exists for the provider (external submission endpoint only) |
| `502` | The submission validator was temporarily unavailable. No record is created — retry the request |
## Typical Integration Flow
Call `GET /questionnaires/{questionnaire_id}` to retrieve the `model` and `theme`.
Render the model with the SurveyJS renderer (or use the RxScale snippet, which does this for you).
Post the collected `data` to `/submissions` (public), or to `/external/submissions` with your `X-API-Key` for external providers. RxScale validates the answers against the model.
Persist the returned `uid` and use it to link the submission to a Shopify order. See the [Questionnaire Integration guide](/guides/questionnaire-integration).
# Orders
Source: https://docs.rxscale.com/api-reference/public/orders
Query order status via the Public API
# Orders
Query the fulfillment status of orders by their associated prescription UIDs.
## Get Order Status
Retrieve order and fulfillment status for one or more prescriptions.
```bash theme={null}
GET /v2/public-api/orders/{shop_identifier}
```
Unique identifier for the shop
Comma-separated list of prescription UIDs (max 50)
**Required permission:** `order:read`
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v2/public-api/orders/my-shop?prescription_uids=px-abc123,px-def456" \
-H "X-API-Key: your-api-key-here"
```
### Response
```json theme={null}
{
"data": [
{
"prescription_uid": "px-abc123",
"order_status": "completed",
"fulfillment_status": "FINISHED",
"tracking_number": "1Z999AA10123456784",
"tracking_url": "https://tracking.example.com/1Z999AA10123456784",
"created_at": "2025-03-15T10:30:00Z",
"fulfilled_at": "2025-03-17T14:00:00Z",
"line_items": [
{
"sku_uid": "sku-456",
"display_name": "Medication X 100mg",
"quantity": 1
}
]
},
{
"prescription_uid": "px-def456",
"order_status": "waiting for pharmacy",
"fulfillment_status": "OPEN",
"tracking_number": null,
"tracking_url": null,
"created_at": "2025-03-16T09:00:00Z",
"fulfilled_at": null,
"line_items": [
{
"sku_uid": "sku-789",
"display_name": "Medication Y 50mg",
"quantity": 2
}
]
},
{
"prescription_uid": "px-ghi789",
"order_status": null,
"fulfillment_status": null,
"tracking_number": null,
"tracking_url": null,
"created_at": "2025-03-16T11:00:00Z",
"fulfilled_at": null,
"line_items": []
}
]
}
```
Unknown prescription UIDs, and prescriptions that are not scoped to your organisation, are **omitted** from `data` (HTTP 200 with an empty list if none match). A prescription that belongs to your organisation but has no order yet is **included**, with `order_status` and usually `fulfillment_status` set to `null`.
### Response Fields
| Field | Type | Description |
| -------------------- | -------------- | ---------------------------------------------------------------------------------------------- |
| `prescription_uid` | string | The prescription UID queried |
| `order_status` | string or null | Stored order status (see values below). `null` when the prescription has no order yet |
| `fulfillment_status` | string or null | Stored fulfillment-order status (see values below). `null` when no fulfillment order is linked |
| `tracking_number` | string or null | Shipping tracking number (if available) |
| `tracking_url` | string or null | Shipping tracking URL (if available) |
| `created_at` | string | Prescription creation timestamp (ISO 8601) |
| `fulfilled_at` | string or null | Fulfillment timestamp (ISO 8601, if fulfilled) |
| `line_items` | array | Items in the order |
### `order_status` values
Returned as stored. Possible values:
| Value | Meaning |
| -------------------------- | --------------------------------- |
| `init` | Order created, not yet progressed |
| `waiting for clearance` | Waiting for clearance |
| `waiting for doctor` | Waiting for a doctor |
| `waiting for pharmacy` | Waiting for the pharmacy |
| `paused` | Paused |
| `completed` | Completed |
| `cancelled` | Cancelled |
| `out of stock` | Out of stock |
| `waiting for manual input` | Waiting for manual input |
| `waiting for payment` | Waiting for payment |
| `pharmacy not found` | Pharmacy not found |
| `prescription declined` | Prescription declined |
| `prescription not found` | Prescription not found |
| `prescription not signed` | Prescription not signed |
| `items missing` | Items missing |
| `reinit` | Order reinitialized |
| `null` | Prescription has no order yet |
### `fulfillment_status` values
Returned as stored. Possible values:
| Value | Meaning |
| ------------ | ------------------------------- |
| `OPEN` | Fulfillment order is open |
| `PROCESSING` | Fulfillment order is processing |
| `CANCELLED` | Fulfillment order is cancelled |
| `FINISHED` | Fulfillment order is finished |
| `null` | No fulfillment order is linked |
### Error Responses
| Status Code | Description |
| ----------- | ------------------------------------------------------------------- |
| `400` | Missing `prescription_uids` parameter or more than 50 UIDs provided |
| `404` | Shop not found |
# Overview
Source: https://docs.rxscale.com/api-reference/public/overview
Public API for product catalogs and checkout creation
# Public API
The Public API provides access to product catalogs, prescription and treatment checkout creation, and order status queries. It is designed for customer-facing integrations such as storefronts and telemedicine platforms.
## Base Path
```
/v2/public-api
```
## Interactive API Documentation (Swagger)
A live Swagger UI is available for exploring and testing Public API endpoints directly in your browser:
```
https://api.rxscale.com/v2/public-api/apidocs
```
The Swagger UI lets you try out API calls interactively. Authenticate with your API key to test against real data.
## Authentication
All endpoints require an API key via the `X-API-Key` header. See [Authentication](/authentication) for details.
```bash theme={null}
curl -X GET "https://api.rxscale.com/v2/public-api/health/" \
-H "X-API-Key: your-api-key-here"
```
The legacy `X-RxScale-Authorization` header and `Authorization: Bearer ` are also supported. `X-API-Key` is recommended unless your platform requires bearer-token authentication.
## Available Endpoints
| Method | Endpoint | Description |
| ------ | ----------------------------------------- | ------------------------------------------------ |
| `GET` | `/products/{shop_identifier}` | List products for a shop |
| `POST` | `/products/{shop_identifier}/live-stock` | Check product stock availability before checkout |
| `POST` | `/products/{shop_identifier}/reservation` | Reserve products in a Shopify draft order |
| `GET` | `/orders/{shop_identifier}` | Get order status by prescription UIDs |
| `POST` | `/prescriptions/{shop_identifier}` | Create a prescription-based checkout |
| `POST` | `/treatments/{shop_identifier}` | Create a treatment-based checkout |
## Required Permissions
| Endpoint | Required Permission |
| ---------------------------- | ------------------------------ |
| List products | `product:read` |
| Check live stock | `product:read` |
| Reserve products | `create_prescription_checkout` |
| Get order status | `order:read` |
| Create prescription checkout | `create_prescription_checkout` |
| Create treatment checkout | `create_treatment_checkout` |
All Public API endpoints are scoped by `shop_identifier` in the URL path. The shop must belong to the organisation associated with your API key.
# Patient Data
Source: https://docs.rxscale.com/api-reference/public/patient
Query patient intent and manage patient properties
# Patient Data
Access patient-specific data such as intent status and custom properties. These endpoints use the shop customer ID and shop identifier to identify patients.
## Check Patient Intent
Query whether a patient has completed a specific intent (e.g., a questionnaire submission).
```bash theme={null}
GET /api/v0/patient/intent
```
The customer ID from your shop system
Your shop identifier (e.g., `my-store.myshopify.com`)
The intent identifier to check
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/api/v0/patient/intent?shop_customer_id=CUST123&shop_identifier=my-store.myshopify.com&intent=weight-loss-questionnaire" \
-H "X-API-Key: your-api-key-here"
```
### Response
```json theme={null}
{
"return_code": "signed_within_12_months"
}
```
The `return_code` indicates when the patient last completed a signed submission for the given intent.
This endpoint returns `404` if the patient is not known to RxScale. If the patient was just created (e.g., during a Shopify checkout), their profile may not be available immediately. Wait 1–2 seconds and retry before treating a `404` as a definitive "patient not found".
## Get Patient Property
Retrieve a specific property value for a patient.
```bash theme={null}
GET /api/v0/patient/properties
```
The customer ID from your shop system
Your shop identifier
The property field name to retrieve. You can find the field key in the admin tool under **Settings > Profile Fields** — use the **key** value shown for each field.
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/api/v0/patient/properties?shop_customer_id=CUST123&shop_identifier=my-store.myshopify.com&field=allergies" \
-H "X-API-Key: your-api-key-here"
```
## Set Patient Property
Set or update a property value for a patient.
```bash theme={null}
POST /api/v0/patient/properties
```
The customer ID from your shop system
Your shop identifier
### Request Body (form-data)
| Field | Type | Required | Description |
| ------- | ------ | -------- | ----------------------- |
| `field` | string | Yes | The property field name |
| `value` | string | Yes | The value to set |
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/api/v0/patient/properties?shop_customer_id=CUST123&shop_identifier=my-store.myshopify.com" \
-H "X-API-Key: your-api-key-here" \
-F "field=allergies" \
-F "value=penicillin"
```
Patient properties are custom fields defined per organisation. You can configure available property fields in the **Settings > Profile Fields** section of the admin tool.
# Prescriptions & Treatments
Source: https://docs.rxscale.com/api-reference/public/prescriptions
Create prescription and treatment checkouts via the Public API
# Prescriptions & Treatments
Create checkout sessions for prescriptions or treatments. These endpoints handle prescription validation, checkout creation, and return a checkout URL or draft order for the patient to complete their purchase.
## Create Prescription Checkout
Upload one or more signed prescriptions (as base64 PDFs) along with line items and patient data to create a checkout.
```bash theme={null}
POST /v2/public-api/prescriptions/{shop_identifier}
```
Unique identifier for the shop
**Required permission:** `create_prescription_checkout`
### Request Body
```json theme={null}
{
"reserved_draft_order_id": "123",
"prescriptions": [
{
"id": "my-prescription-001",
"pdf_base64": "JVBERi0xLjQK..."
}
],
"lines": [
{
"sku_uid": "sku-456",
"quantity": 1,
"prescription_id": "my-prescription-001"
}
],
"patient_data": {
"first_name": "Max",
"last_name": "Mustermann",
"date_of_birth": 631152000,
"gender": "male"
},
"buyerIdentity": {
"email": "max.mustermann@example.com",
"phone": "+4917612345678",
"countryCode": "DE",
"customerAccessToken": "shopify-customer-access-token"
},
"checkout_type": "draft_order",
"prepaid": false,
"external_order_id": "order-123",
"delivery": {
"delivery_type": "pickup",
"delivery_price": 499
},
"transaction": {
"transaction_id": "txn-123",
"transaction_amount": 4299
},
"billing_address": {
"first_name": "Max",
"last_name": "Mustermann",
"address1": "Hauptstraße 42",
"city": "Berlin",
"province": "",
"country_code": "DE",
"zip": "10115"
},
"shipping_address": {
"first_name": "Max",
"last_name": "Mustermann",
"address1": "Hauptstraße 42",
"city": "Berlin",
"province": "",
"country_code": "DE",
"zip": "10115"
}
}
```
| Field | Type | Required | Description |
| ----------------------------------- | ------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `reserved_draft_order_id` | string | No | Shopify legacy DraftOrder ID returned by the product reservation endpoint. When present, RxScale updates that draft order instead of creating a new checkout |
| `prescriptions` | array | No | List of prescription objects with `id` and `pdf_base64` |
| `prescriptions[].id` | string | Yes | Your internal ID for this prescription (used to link line items) |
| `prescriptions[].pdf_base64` | string | Yes | Base64-encoded PDF of the signed prescription |
| `lines` | array | No | Line items for the checkout |
| `lines[].sku_uid` | string | Conditional | RxScale SKU UID from the product catalog. At least one of `sku_uid` or `external_id` is required per line |
| `lines[].external_id` | string | Conditional | Your telemedicine provider product ID (`telemedicine_provider_data.external_id` from the product catalog). Resolved using the provider linked to the API key. When both identifiers are sent, `sku_uid` takes precedence |
| `lines[].quantity` | integer | Yes | Quantity to order |
| `lines[].prescription_id` | string | No | Links the line item to a prescription by its `id` |
| `patient_data` | object | Yes | Patient demographic data |
| `patient_data.first_name` | string | Yes | Patient first name |
| `patient_data.last_name` | string | Yes | Patient last name |
| `patient_data.date_of_birth` | integer | Yes | Date of birth as Unix timestamp |
| `patient_data.gender` | string | Yes | Patient gender (`male`, `female`, `divers`) |
| `buyerIdentity` | object | Conditional | Shopify cart buyer identity. Required when `checkout_type` is `draft_order_without_checkout_request`; used for `checkout_link` carts, draft-order checkout requests, and to overwrite email/phone on a reserved draft order |
| `buyerIdentity.email` | string | No | Buyer email address |
| `buyerIdentity.phone` | string | No | Buyer phone number |
| `buyerIdentity.countryCode` | string | No | Shopify country code for the buyer |
| `buyerIdentity.customerAccessToken` | string | No | Shopify Storefront customer access token. When provided with `checkout_type: "checkout_link"`, Shopify associates the cart with the customer's account |
| `checkout_type` | string | No | Checkout type: `checkout_link`, `draft_order` (default), or `draft_order_without_checkout_request` |
| `prepaid` | boolean | No | Whether the order has already been paid externally. Defaults to `false` and is attached to Shopify order attributes as `prepaid=true` or `prepaid=false`. It does not change `checkout_type` behavior |
| `external_order_id` | string | No | Your external order identifier. When present, RxScale attaches it to Shopify order attributes as `external_order_id` |
| `delivery` | object | No | Delivery metadata to attach to the Shopify order attributes |
| `delivery.delivery_type` | string | No | Delivery type. The store owner maps this value to their own shipping methods |
| `delivery.delivery_price` | integer | No | Delivery price to attach to the Shopify order attributes |
| `transaction` | object | No | Transaction metadata to attach to the Shopify order attributes |
| `transaction.transaction_id` | string | No | Transaction identifier to attach to the Shopify order attributes |
| `transaction.transaction_amount` | integer | No | Transaction amount to attach to the Shopify order attributes |
| `billing_address` | object | No | Billing address attached to the Shopify draft order. Ignored when `checkout_type` is `checkout_link` |
| `billing_address.first_name` | string | No | Recipient first name |
| `billing_address.last_name` | string | No | Recipient last name |
| `billing_address.address1` | string | No | Single-line street address including house number |
| `billing_address.city` | string | No | City |
| `billing_address.province` | string | No | State, province, or region. Populated for countries like IE; sent as an empty string for countries like DE |
| `billing_address.country_code` | string | No | ISO country code |
| `billing_address.zip` | string | No | Postal code |
| `shipping_address` | object | No | Shipping address attached to the Shopify draft order. Ignored when `checkout_type` is `checkout_link` |
| `shipping_address.first_name` | string | No | Recipient first name |
| `shipping_address.last_name` | string | No | Recipient last name |
| `shipping_address.address1` | string | No | Single-line street address including house number |
| `shipping_address.city` | string | No | City |
| `shipping_address.province` | string | No | State, province, or region. Populated for countries like IE; sent as an empty string for countries like DE |
| `shipping_address.country_code` | string | No | ISO country code |
| `shipping_address.zip` | string | No | Postal code |
`buyerIdentity.customerAccessToken` only applies when `checkout_type` is `checkout_link`, because Shopify uses it on Storefront carts. It does not attach a customer account to Shopify draft orders.
Send `buyerIdentity` when using `checkout_type: "draft_order_without_checkout_request"`. RxScale rejects the request without it because no Shopify checkout request is sent to collect customer details later.
### Checkout Types
The `checkout_type` field controls how the order is created in Shopify:
| Value | Description |
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `draft_order` | Creates a Shopify draft order and sends a checkout request to the customer (default) |
| `checkout_link` | Returns a Shopify checkout link for the customer to complete payment directly |
| `draft_order_without_checkout_request` | Creates a Shopify draft order without sending a checkout request to the customer. Requires `buyerIdentity` because customer details are not collected through a Shopify checkout request |
If `reserved_draft_order_id` is present, `checkout_type` is ignored. RxScale stores the signed prescriptions and adds `_prescription_uid` metadata to the matching reserved draft-order **line items** (not order-level attributes). Matching is based on the Shopify variant resolved from `sku_uid`; duplicate `sku_uid` values are rejected because they are ambiguous. Keys starting with `_` are private Shopify properties and are often hidden in the Shopify Admin UI — verify via the Admin GraphQL API if needed. When `buyerIdentity.email` and/or `buyerIdentity.phone` are provided (and non-empty), they overwrite the reserved draft order's buyer contact details; empty values are ignored so existing contact details are never cleared. The request's `billing_address` and `shipping_address` are forwarded to the draft order, with the patient's name from `patient_data` filled into each address where none is supplied (Shopify draft orders have no separate customer-name field). The reserved draft order must belong to the telemedicine provider linked to the API key.
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v2/public-api/prescriptions/my-shop" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"reserved_draft_order_id": "123",
"prescriptions": [
{
"id": "rx-001",
"pdf_base64": "JVBERi0xLjQK..."
}
],
"lines": [
{
"sku_uid": "sku-456",
"quantity": 1,
"prescription_id": "rx-001"
}
],
"patient_data": {
"first_name": "Max",
"last_name": "Mustermann",
"date_of_birth": 631152000,
"gender": "male"
},
"buyerIdentity": {
"email": "max.mustermann@example.com",
"phone": "+4917612345678",
"countryCode": "DE",
"customerAccessToken": "shopify-customer-access-token"
},
"prepaid": false,
"external_order_id": "order-123",
"delivery": {
"delivery_type": "pickup",
"delivery_price": 499
},
"transaction": {
"transaction_id": "txn-123",
"transaction_amount": 4299
},
"billing_address": {
"first_name": "Max",
"last_name": "Mustermann",
"address1": "Hauptstraße 42",
"city": "Berlin",
"province": "",
"country_code": "DE",
"zip": "10115"
},
"shipping_address": {
"first_name": "Max",
"last_name": "Mustermann",
"address1": "Hauptstraße 42",
"city": "Berlin",
"province": "",
"country_code": "DE",
"zip": "10115"
}
}'
```
### Response
```json theme={null}
{
"status": "success",
"prescriptions": [
{
"id": "rx-001",
"prescription_uid": "px-abc123"
}
]
}
```
The response maps your prescription IDs to the RxScale prescription UIDs. Use these UIDs to query order status via the [Orders endpoint](/api-reference/public/orders).
When `reserved_draft_order_id` is used, no new checkout or draft order is created. The existing reserved draft order is updated and can continue through the normal Shopify order and fulfillment process after payment.
### Error Responses
| Status Code | Description |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | The same `prescriptions[].id` appears more than once in the request payload |
| `409` | A `prescriptions[].id` was already successfully accepted for this telemedicine provider. No new prescription, PDF, or order is created |
| `422` | A prescription PDF is not digitally signed (QES) and unsigned prescriptions are not allowed for your provider |
| `400` | The signature-validation service was unavailable, so the prescription signature could not be validated. No prescription or order is created — safe to retry |
```json theme={null}
{
"error": "Duplicate prescription injection: prescription id(s) ['rx-123'] already submitted by provider your-provider (existing prescription(s) ['px-abc123']); no order created."
}
```
A `prescriptions[].id` is only blocked once it has been **successfully** accepted. If an earlier request with that `id` failed (for example, it returned a 4xx/5xx before completing), resubmitting the same `id` is not blocked and is processed normally. This makes prescription injection safe to retry after a failed or uncertain request — a repeat submission of an already-accepted `id` returns `409` instead of silently creating a duplicate order, so it can be treated as an idempotent no-op rather than an error to alert on.
This check applies to every prescription-injection path that shares this entrypoint, including provider-specific integrations (for example medcanonestop, dransay) and reserved draft-order updates (`reserved_draft_order_id`).
## Create Treatment Checkout
Create a checkout for treatment-based orders (no prescription required).
```bash theme={null}
POST /v2/public-api/treatments/{shop_identifier}
```
Unique identifier for the shop
**Required permission:** `create_treatment_checkout`
### Request Body
```json theme={null}
{
"lines": [
{
"sku_uid": "sku-789",
"quantity": 1,
"anamnesis_id": "anam-001"
}
],
"buyerIdentity": {
"email": "max.mustermann@example.com",
"phone": "+4917612345678",
"countryCode": "DE",
"customerAccessToken": "shopify-customer-access-token"
},
"checkout_type": "draft_order"
}
```
| Field | Type | Required | Description |
| ----------------------------------- | ------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `lines` | array | Yes | Line items for the checkout |
| `lines[].sku_uid` | string | Yes | SKU UID from the product catalog |
| `lines[].quantity` | integer | Yes | Quantity to order |
| `lines[].anamnesis_id` | string | No | Anamnesis ID for linking to a patient questionnaire |
| `buyerIdentity` | object | Conditional | Shopify cart buyer identity. Required when `checkout_type` is `draft_order_without_checkout_request`; used for `checkout_link` carts, draft-order checkout requests, and to overwrite email/phone on a reserved draft order |
| `buyerIdentity.email` | string | No | Buyer email address |
| `buyerIdentity.phone` | string | No | Buyer phone number |
| `buyerIdentity.countryCode` | string | No | Shopify country code for the buyer |
| `buyerIdentity.customerAccessToken` | string | No | Shopify Storefront customer access token. When provided with `checkout_type: "checkout_link"`, Shopify associates the cart with the customer's account |
| `checkout_type` | string | No | Checkout type: `checkout_link`, `draft_order` (default), or `draft_order_without_checkout_request` |
`buyerIdentity.customerAccessToken` only applies when `checkout_type` is `checkout_link`, because Shopify uses it on Storefront carts. It does not attach a customer account to Shopify draft orders.
Send `buyerIdentity` when using `checkout_type: "draft_order_without_checkout_request"`. RxScale rejects the request without it because no Shopify checkout request is sent to collect customer details later.
See [Checkout Types](#checkout-types) above for details on each option.
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v2/public-api/treatments/my-shop" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"lines": [
{
"sku_uid": "sku-789",
"quantity": 1
}
],
"buyerIdentity": {
"email": "max.mustermann@example.com",
"phone": "+4917612345678",
"countryCode": "DE",
"customerAccessToken": "shopify-customer-access-token"
}
}'
```
### Response
```json theme={null}
{
"status": "success",
"checkout_url": "https://shop.example.com/checkout/abc123"
}
```
# Products
Source: https://docs.rxscale.com/api-reference/public/products
List products for a shop via the Public API
# Products
Retrieve a paginated list of products available in a specific shop, filtered by market.
## List Products
```bash theme={null}
GET /v2/public-api/products/{shop_identifier}
```
Unique identifier for the shop
Market identifier (e.g. `DE` for Germany)
Filter products and SKUs by name
Page number (0-indexed)
Results per page (max 150)
**Required permission:** `product:read`
### Example Request
```bash theme={null}
curl -X GET "https://api.rxscale.com/v2/public-api/products/my-shop?market=DE&page=0&limit=25" \
-H "X-API-Key: your-api-key-here"
```
### Response
```json theme={null}
{
"data": [
{
"uid": "prod-abc123",
"display_name": "Medication X",
"attributes": {
"category": "Pain Relief"
},
"skus": [
{
"uid": "sku-456",
"display_name": "Medication X 100mg",
"attributes": {
"package_size": "100mg"
},
"availability": "High",
"telemedicine_provider_data": {
"external_id": "partner-sku-456"
},
"price": 1299,
"currency": "EUR",
"unit": "g",
"standard_selling_unit": 10
},
{
"uid": "sku-789",
"display_name": "Medication X 200mg",
"attributes": {
"package_size": "200mg"
},
"availability": "Medium",
"telemedicine_provider_data": {
"external_id": "partner-sku-789"
},
"price": 1999,
"currency": "EUR",
"unit": "g",
"standard_selling_unit": 20
}
]
}
],
"totalRegistries": 42,
"totalPages": 2
}
```
### Response Fields
| Field | Type | Description |
| ------------------------------------------------------ | ------- | --------------------------------------------------------------- |
| `data` | array | List of product objects |
| `data[].uid` | string | Product UID |
| `data[].display_name` | string | Product display name |
| `data[].attributes` | object | Product attributes as key-value pairs |
| `data[].skus` | array | Available SKUs for this product in the given market |
| `data[].skus[].uid` | string | SKU UID (use this when creating checkouts) |
| `data[].skus[].display_name` | string | SKU display name |
| `data[].skus[].attributes` | object | SKU attributes as key-value pairs |
| `data[].skus[].availability` | string | Availability bucket (`High`, `Medium`, `Low`, or `Unavailable`) |
| `data[].skus[].telemedicine_provider_data.external_id` | string | External SKU identifier for the telemedicine provider |
| `data[].skus[].price` | integer | Price in euro cents |
| `data[].skus[].currency` | string | Price currency |
| `data[].skus[].unit` | string | SKU unit |
| `data[].skus[].standard_selling_unit` | number | Standard selling unit |
| `totalRegistries` | integer | Total number of matching products |
| `totalPages` | integer | Total number of pages |
### Error Responses
| Status Code | Description |
| ----------- | -------------------------------- |
| `400` | Missing `market` query parameter |
| `404` | Shop not found |
## Check Live Stock
Check whether one or more SKUs are currently available in the requested quantities before the checkout starts.
```bash theme={null}
POST /v2/public-api/products/{shop_identifier}/live-stock
```
Unique identifier for the shop
**Required permission:** `product:read`
Live stock is checked against Shopify's current available inventory for the mapped product variants. This includes inventory already held by Shopify orders and draft-order reservations.
### Request Body
```json theme={null}
[
{
"sku_uid": "sku-456",
"quantity": 2
},
{
"sku_uid": "sku-789",
"quantity": 1
}
]
```
| Field | Type | Required | Description |
| ------------- | ------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sku_uid` | string | Conditional | RxScale SKU UID from the product catalog. At least one of `sku_uid` or `external_id` is required per line |
| `external_id` | string | Conditional | Your telemedicine provider product ID. Resolved using the provider linked to the API key. When both identifiers are sent, `sku_uid` takes precedence |
| `quantity` | integer | Yes | Quantity to check. Must be at least `1` |
Each resolved `sku_uid` can appear only once per live stock request.
When you send `external_id`, the API key must be linked to a telemedicine provider. Unknown external IDs return `404` with `{"error": "Product with external ID '…' not found"}`.
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v2/public-api/products/my-shop/live-stock" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '[
{
"sku_uid": "sku-456",
"quantity": 2
}
]'
```
### Available Response
When all requested quantities are available, the endpoint returns `200 OK`.
```json theme={null}
{
"available": true,
"lines": [
{
"sku_uid": "sku-456",
"quantity": 2,
"available": true,
"available_stock": 12
}
]
}
```
### Unavailable Response
When at least one requested quantity is not available, the endpoint returns `409 Conflict`.
```json theme={null}
{
"available": false,
"lines": [
{
"sku_uid": "sku-456",
"quantity": 2,
"available": false,
"available_stock": 1
}
]
}
```
### Response Fields
| Field | Type | Description |
| ------------------------- | ------- | ------------------------------------------------------- |
| `available` | boolean | `true` only when all requested quantities are available |
| `lines[].sku_uid` | string | Requested SKU UID |
| `lines[].quantity` | integer | Requested quantity |
| `lines[].available` | boolean | Whether this SKU has enough stock |
| `lines[].available_stock` | integer | Current stock available for the SKU in the shop |
### Error Responses
| Status Code | Description |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | Body validation failed, duplicate resolved `sku_uid` values, or API key not linked to a telemedicine provider when using `external_id` |
| `404` | Shop, SKU, or unknown `external_id` not found |
| `409` | At least one requested quantity is unavailable |
## Reserve Products
Create a Shopify draft order with a 24-hour inventory reservation before prescriptions are available. Use the returned draft order ID later as `reserved_draft_order_id` when uploading signed prescriptions.
```bash theme={null}
POST /v2/public-api/products/{shop_identifier}/reservation
```
Unique identifier for the shop
**Required permission:** `create_prescription_checkout`
The reservation request does not include prescriptions or patient data. RxScale adds telemedicine provider attributes from the API key automatically.
The API key must be linked to a telemedicine provider. Requests with a non-telemedicine API key are rejected with `400 Bad Request` before any inventory is held, because the resulting draft order could not be matched on the later prescription upload.
### Request Body
```json theme={null}
{
"external_reservation_id": "caller-reservation-123",
"lines": [
{
"sku_uid": "sku-456",
"quantity": 1
}
]
}
```
| Field | Type | Required | Description |
| ------------------------- | ------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `external_reservation_id` | string | No | Your reservation identifier. Stored on the Shopify draft order and returned in the response |
| `lines` | array | Yes | Products to reserve |
| `lines[].sku_uid` | string | Conditional | RxScale SKU UID from the product catalog. At least one of `sku_uid` or `external_id` is required per line |
| `lines[].external_id` | string | Conditional | Your telemedicine provider product ID. Resolved using the provider linked to the API key. When both identifiers are sent, `sku_uid` takes precedence |
| `lines[].quantity` | integer | Yes | Quantity to reserve |
Each resolved `sku_uid` can appear only once per reservation request. Duplicate SKU lines are rejected because the later prescription upload must match each reserved draft-order line unambiguously.
### Example Request
```bash theme={null}
curl -X POST "https://api.rxscale.com/v2/public-api/products/my-shop/reservation" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"external_reservation_id": "caller-reservation-123",
"lines": [
{
"sku_uid": "sku-456",
"quantity": 1
}
]
}'
```
### Response
```json theme={null}
{
"status": "success",
"external_reservation_id": "caller-reservation-123",
"draft_order": {
"id": "123",
"invoiceUrl": "https://example.myshopify.com/invoice/...",
"reserve_inventory_until": "2026-05-16T17:20:00Z"
}
}
```
The `draft_order.id` is the Shopify legacy DraftOrder ID, not the Shopify GID. Pass this value to `POST /v2/public-api/prescriptions/{shop_identifier}` as `reserved_draft_order_id` after the prescriptions are signed.
### Error Responses
| Status Code | Description |
| ----------- | ---------------------------------------------------------------------------------------------------------------- |
| `400` | API key is not linked to a telemedicine provider, body validation failed, or duplicate resolved `sku_uid` values |
| `404` | Shop, SKU, or unknown `external_id` not found |
# Scheduling
Source: https://docs.rxscale.com/api-reference/scheduling/appointments
Let patients discover appointment types, book slots, and join video appointments
# Scheduling
The Scheduling API lets telemedicine partners offer patient self-booking for video appointments. Public discovery endpoints can be called with a `shop_uid`. Booking and rebooking operations use either an organisation API key or a short-lived patient booking token.
Never put signed patient booking tokens in URLs. Exchange the token for a `booking_launch_code` and use that code for hosted booking links or embedded widgets.
## Authentication
The Scheduling API accepts two credentials. Most endpoints accept either; some require a specific one.
### Patient booking tokens (recommended for patient flows)
A short-lived JWT minted by the partner backend for a specific patient. Sent in the `X-RxScale-Booking-Token` header. The token is verified against an organisation booking-token secret provisioned by RxScale.
**Algorithm:** `HS256`
**JOSE header:** must include `kid` — the `key_id` returned when you provisioned the booking-token secret.
**Required claims:**
| Claim | Type | Description |
| ------------------------ | ------ | --------------------------------------------------------------------------------------------------------------- |
| `aud` | string | Must be `rxscale-scheduling`. |
| `exp` | int | Expiration (Unix seconds). Keep this short — under 5 minutes is typical. |
| `nbf` | int | Not-before (Unix seconds). |
| `iat` | int | Issued-at (Unix seconds). |
| `shop_identifier` | string | The shop identifier you configured in RxScale. |
| `shop_customer_id` | string | Your stable customer identifier. RxScale uses it to deduplicate patients across booking sessions. |
| `patient_profile_fields` | array | Patient profile fields to upsert. Each entry: `{ "field": "", "value": , "source": "shop" }`. |
```python theme={null}
import jwt, time
now = int(time.time())
payload = {
"aud": "rxscale-scheduling",
"exp": now + 300,
"nbf": now - 10,
"iat": now,
"shop_identifier": "shop_abc",
"shop_customer_id": "cust_123",
"patient_profile_fields": [
{"field": "first_name", "value": "Ada", "source": "shop"},
{"field": "last_name", "value": "Lovelace", "source": "shop"},
{"field": "email", "value": "ada@example.com", "source": "shop"},
],
}
token = jwt.encode(payload, SECRET, algorithm="HS256", headers={"kid": KEY_ID})
```
Booking-token secrets are stored **encrypted at rest** in the RxScale database. Only your `key_id` is stored in plaintext for routing; the secret value is decrypted in-memory both at verification time and whenever an admin reads the value back through the admin portal or the secrets API. Rotate by provisioning a new secret and revoking the old one once your minter has switched over.
### Organisation API key
Send `X-API-Key: ` instead of the booking token for server-to-server flows. The key needs the `scheduling:write` permission. Use this for back-office tooling, scripted rebooking, or operational scripts.
## List Appointment Types
```bash theme={null}
GET /v1/scheduling/appointment-types?shop_uid={shop_uid}
```
Shop UID used to scope appointment types to the correct organisation.
No API key is required.
```bash theme={null}
curl "https://api.rxscale.com/v1/scheduling/appointment-types?shop_uid=shop_123"
```
```json theme={null}
{
"data": [
{
"uid": "apt_video_15",
"name": "Video consultation",
"duration_minutes": 15
}
]
}
```
The response intentionally omits `hold_ttl_seconds`, `max_bookings_per_slot`, and every other
scheduling and pricing field on the appointment type -- this endpoint is unauthenticated, and those
fields are exactly what an attacker would tune a slot-holding campaign against. If your integration
needs them, use `GET /v1/management/scheduling/appointment-types` with an organisation API key
instead (see the Management API docs).
## Search Slots
```bash theme={null}
POST /v1/scheduling/slots/search
```
No API key is required.
Slot search only returns slots that satisfy the appointment type's effective
booking notice. If a doctor has a doctor-specific override for that appointment
type, the doctor's value takes precedence, including `0` minutes. Appointment
types configured as `selected_doctors_only` only return doctors with an active
doctor-specific setting.
Window start (Unix seconds).Window end (Unix seconds). Max 31 days from `from`.Restrict to a single doctor.IANA timezone (e.g. `Europe/Berlin`) used to localize the output. Defaults to `Europe/Berlin`. Each slot includes `start_local`/`end_local` rendered in this zone; `start_date`/`end_date` remain Unix seconds.
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/scheduling/slots/search" \
-H "Content-Type: application/json" \
-d '{
"shop_uid": "shop_123",
"appointment_type_uid": "apt_video_15",
"from": 1778457600,
"to": 1778544000
}'
```
```json theme={null}
{
"slots": [
{
"doctor_uid": "doc_123",
"doctor_name": "Dr. Max Meyer",
"start_date": 1778490000,
"end_date": 1778490900,
"timezone": "Europe/Berlin",
"start_local": "2026-05-11T11:00:00+02:00",
"end_local": "2026-05-11T11:15:00+02:00"
}
]
}
```
## Create Booking Session
Use this endpoint when launching the hosted UI or script widget. The request can include the signed patient booking token either in `X-RxScale-Booking-Token` or in the JSON body as `booking_token`.
```bash theme={null}
POST /v1/scheduling/booking-sessions
```
`booking` for a new appointment, `rebooking` for an existing one.Required for `booking` mode.Window start (Unix seconds). Required for `booking` mode.Window end (Unix seconds). Required for `booking` mode.URL to redirect the patient to after a successful booking.Patient booking JWT, if not sent via header.
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/scheduling/booking-sessions" \
-H "Content-Type: application/json" \
-H "X-RxScale-Booking-Token: eyJ..." \
-d '{
"mode": "booking",
"appointment_type_uid": "apt_video_15",
"from": 1778457600,
"to": 1778544000,
"return_url": "https://partner.example/booking-complete"
}'
```
```json theme={null}
{
"booking_launch_code": "blc_Nc8...",
"expires_at": 1778458500,
"launch_url": "https://meet.rxscale.com/booking?launch=blc_Nc8..."
}
```
## Get Booking Session
The hosted UI calls this with just the launch code to load the booking context. No authentication header is needed — the launch code itself is the credential.
```bash theme={null}
GET /v1/scheduling/booking-sessions/{booking_launch_code}
```
```bash theme={null}
curl "https://api.rxscale.com/v1/scheduling/booking-sessions/blc_Nc8..."
```
```json theme={null}
{
"uid": "bsn_abc...",
"shop_uid": "shop_123",
"appointment_type_uid": "apt_video_15",
"appointment_type": {
"uid": "apt_video_15",
"name": "Video consultation",
"duration_minutes": 15
},
"start_from": 1778457600,
"start_to": 1778544000,
"mode": "booking",
"return_url": "https://partner.example/booking-complete",
"expires_at": 1778458500,
"billing": {
"lines": [
{
"description": "Video consultation",
"quantity": 1,
"unit_amount": 4900,
"vat_rate_bp": null
}
],
"total": 4900,
"currency": "EUR",
"payment_required": true,
"test_mode": false
}
}
```
The response intentionally omits `patient_profile_uid` and other patient identifiers, so the launch code can be safely passed through URLs in the patient's browser.
### Billing
`billing` is always present and describes what the booking session costs.
* `lines` — the priced positions, in display order. `unit_amount` is in the smallest unit
of the currency (cents), and `vat_rate_bp` is a VAT rate in basis points (`1900` = 19%).
`null` means no VAT applies, which is distinct from a real 0% rate.
* `total` — the sum of `quantity × unit_amount` over all lines, in the same unit.
* `currency` — the ISO 4217 code every line is priced in. It is `null` only when there is
no currency to report at all: a session with no lines *and* no appointment type. A
session that has lines always has a currency, so treat `null` as "nothing to charge"
rather than as a default to fall back on.
* `payment_required` — `false` when there are no lines. Branch on this single flag: a
session with `payment_required: false` is booked exactly as before, with no payment
step. Sessions are unpriced unless the booking link was generated with prices.
* `test_mode` — `true` when this booking's payment goes to the payment provider in **test
mode**: the checkout page behaves like the real one, but the patient is never charged and
no money reaches the organisation. Tell the patient — the RxScale hosted booking UI shows a
banner on the payment step. It is always `false` when `payment_required` is `false`, because
an unpriced link takes no payment and so has no mode at all. API builds from before this
field shipped omit the key entirely; treat a missing `test_mode` as `false`. It is re-derived
from the appointment type on every request, so it describes the payment that is *about to be*
created — once a payment exists, read [`testmode` on the payment](#start-the-payment) instead.
Prices are fixed when the booking link is generated and never change afterwards, so what
you show here is what the patient is asked to pay.
Test mode is switched on per appointment type by the organisation, in the RxScale admin
portal, so a booking link is either entirely a rehearsal or entirely real — never a mix. A
session for an appointment type in test mode reports it like this:
```bash theme={null}
curl "https://api.rxscale.com/v1/scheduling/booking-sessions/blc_Tm4..."
```
```json theme={null}
{
"uid": "bsn_def...",
"shop_uid": "shop_123",
"appointment_type_uid": "apt_video_15",
"appointment_type": {
"uid": "apt_video_15",
"name": "Video consultation",
"duration_minutes": 15
},
"start_from": 1778457600,
"start_to": 1778544000,
"mode": "booking",
"return_url": "https://partner.example/booking-complete",
"expires_at": 1778458500,
"billing": {
"lines": [
{
"description": "Video consultation",
"quantity": 1,
"unit_amount": 4900,
"vat_rate_bp": null
}
],
"total": 4900,
"currency": "EUR",
"payment_required": true,
"test_mode": true
}
}
```
Nothing else about the session changes: the price, the currency and the whole booking flow are
the same as they would be for a real payment. Only the money is not real.
## Create and Confirm Holds
Authenticated integrations can create holds with an API key that has `scheduling:write`. Hosted UI integrations use the `booking_launch_code` routes.
```bash theme={null}
POST /v1/scheduling/booking-sessions/{booking_launch_code}/holds
```
Unix seconds.Optional reason for the appointment, up to 2000 characters. Blank values are stored as `null`. If the session was created from a public booking link — the unauthenticated, magic-link-verified booking flow — the limit is 500 characters instead, and the value is rejected with `400` if it contains a URL — including one disguised with invisible or full-width characters — or any control, formatting or text-direction character.Optional. Repeating the same key with the same body returns the existing hold instead of creating a duplicate.
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/scheduling/booking-sessions/blc_Nc8.../holds" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: hold-attempt-1" \
-d '{
"doctor_uid": "doc_123",
"appointment_type_uid": "apt_video_15",
"start_date": 1778490000,
"visit_reason": "Medication review before changing dosage"
}'
```
```json theme={null}
{
"hold_uid": "pdm_123",
"expires_at": 1778490900,
"ttl_seconds": 900,
"status": "held",
"visit_reason": "Medication review before changing dosage"
}
```
```bash theme={null}
POST /v1/scheduling/booking-sessions/{booking_launch_code}/holds/{hold_uid}/confirm
```
Optional reason for the appointment, up to 2000 characters. Omit this field to keep the value set when the hold was created; sending a blank value clears it. The same public-booking-link bound applies here: 500 characters, rejected with `400` if the value contains a URL — including one disguised with invisible or full-width characters — or any control, formatting or text-direction character.
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/scheduling/booking-sessions/blc_Nc8.../holds/pdm_123/confirm" \
-H "Content-Type: application/json" \
-d '{
"visit_reason": "Medication review before changing dosage"
}'
```
```json theme={null}
{
"uid": "pdm_123",
"status": "confirmed",
"start_date": 1778490000,
"end_date": 1778490900,
"visit_reason": "Medication review before changing dosage"
}
```
If a hold has expired but no other appointment has taken the slot, confirmation is still allowed and the appointment moves to `confirmed`.
When present, `visit_reason` is visible to doctors/admins and can appear in appointment reminder email/SMS content and synced calendar invite descriptions. It can be set at hold time, and edited or cleared when confirming — a request body without `visit_reason` leaves the existing value untouched.
## Pay for a Hold
Priced booking links (`billing.payment_required: true`) need a payment before the appointment
is confirmed. The patient is sent to a Mollie hosted checkout page; RxScale confirms the
appointment itself once the money has actually arrived. There is no confirm call on this path —
do **not** call the confirm endpoint for a priced session.
Payments are collected on the organisation's own connected Mollie account. The organisation
connects it once, in the RxScale admin portal.
**Test payments.** When `billing.test_mode` is `true`, the payment is created in the payment
provider's test mode: the checkout page looks and behaves exactly like the real one, but nothing
is charged, nothing is paid out to the organisation, and there is nothing to reconcile.
Everything else on this page — the status values, the polling contract, the callback, the refund
behaviour — works identically, which is what makes a rehearsal worth running. The RxScale
platform fee is still shown on the payment, but no fee is actually kept, because no money moved.
See [Rehearsing paid bookings before you go live](/for-telemedicine-providers/admin-appointments#verify-payments-test-mode).
### Start the payment
```bash theme={null}
POST /v1/scheduling/booking-sessions/{booking_launch_code}/payment
```
UID of the hold created for this session.
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/scheduling/booking-sessions/blc_Nc8.../payment" \
-H "Content-Type: application/json" \
-d '{"hold_uid": "pdm_123"}'
```
```json theme={null}
{
"payment_uid": "apy_123",
"checkout_url": "https://www.mollie.com/checkout/select-method/...",
"status": "open",
"amount": 4900,
"currency": "EUR",
"testmode": false
}
```
Send the patient to `checkout_url`. `amount` is in the smallest currency unit — 4900 is €49.00.
`testmode` is the mode **this payment** was created in. It is fixed at creation and never
re-derived, so it is what you read on every screen that follows a payment — not `billing.test_mode`,
which reports the appointment type's *current* mode and can change while the patient is still at
the checkout page. API builds that predate the field omit it; treat a missing `testmode` as "no
answer" and fall back to `billing.test_mode`, never as `false`.
Starting a payment extends the hold so it cannot lapse while the patient is on the checkout
page. Calling this twice for the same hold returns the **same** `checkout_url` with `200`
instead of `201`, so a double-click or a browser back-button cannot produce two payable links
for one appointment.
| Status | Meaning |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `201` | Payment created. |
| `200` | A payment was already open for this hold; the same `checkout_url` is returned. |
| `400` | `hold_uid` missing. |
| `404` | Unknown booking session, or the hold is not this session's, or it is no longer held. |
| `409` | This booking link carries no price, so there is nothing to pay. Use the confirm endpoint. — or the appointment has no appointment type, so it can never be completed. |
| `422` | The organisation cannot currently accept payments. Either no Mollie account is connected, the connection is not in a connected state, or it is misconfigured — and, for a **live** appointment type only, Mollie has not yet enabled the account for payments. An appointment type in test mode is exempt from that last reason alone: it still needs a connected, correctly configured Mollie account. |
| `502` | The payment provider refused the request or could not be reached. Safe to retry. |
### Poll the payment status
```bash theme={null}
GET /v1/scheduling/booking-sessions/{booking_launch_code}/payment?hold_uid=pdm_123
```
```bash theme={null}
curl "https://api.rxscale.com/v1/scheduling/booking-sessions/blc_Nc8.../payment?hold_uid=pdm_123"
```
```json theme={null}
{
"status": "paid",
"amount": 4900,
"currency": "EUR",
"testmode": false,
"appointment_status": "confirmed",
"appointment_start": 1778490000,
"doctor_display_name": "Dr. Test"
}
```
Poll this after the patient returns from checkout. It re-reads the payment from the provider
when needed, so it does not depend on provider callbacks arriving on time — the answer is
correct even if a callback is delayed or lost. Poll every couple of seconds; the endpoint is
cheap and only calls the provider when the record could still have changed.
`appointment_status` is what you branch on. It becomes `confirmed` once the money has arrived
and the slot was still available.
`appointment_start` (epoch seconds) and `doctor_display_name` describe the booked appointment,
so you can show the patient what they just paid for without a second request. Both are `null`
until `appointment_status` is `confirmed` — including while the payment is still `open`, and in
the `refund_required` case, where no appointment exists to describe. `doctor_display_name` may
also be `null` for a confirmed appointment whose doctor has since left the practice, so treat
it as best-effort and fall back to a generic label.
| `status` | Meaning |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `open` | Not paid yet. The patient may still be on the checkout page. |
| `paid` | Paid. Check `appointment_status` — it will be `confirmed`. |
| `failed` / `canceled` / `expired` | Not paid. The hold is left alone; the patient can retry while it lasts. |
| `refund_required` | Paid, but the appointment could not be confirmed — its slot was taken, or it had been cancelled, while the payment was in flight. **No appointment exists.** Ask the patient to book again; RxScale refunds the payment. |
`refund_required` is rare but real. Treat it as "payment succeeded, booking did not" and never as
a successful booking.
### Payment provider callback
```bash theme={null}
POST /v1/scheduling/payments/mollie/webhook/{webhook_token}
```
This endpoint exists for the payment provider, not for partners — there is nothing to
integrate and nothing to call. It is documented only so the request is recognisable in
network logs.
It is unauthenticated in the usual sense: the provider signs nothing. What protects it is the
`webhook_token` in the path, which is a long random value generated per payment, never
returned by any API response, and known only to RxScale and the payment provider. The request
body is used solely to identify which payment changed; the actual outcome is always read back
from the provider over an authenticated call, so a forged request cannot mark anything as
paid.
## Cancel Appointment
Cancel a confirmed or held appointment. Accepts either a booking token (patient-driven cancel) or an organisation API key (operations).
```bash theme={null}
POST /v1/scheduling/appointments/{appointment_uid}/cancel
```
Optional free-text reason. Stored on the appointment for audit.
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/scheduling/appointments/pdm_123/cancel" \
-H "X-RxScale-Booking-Token: eyJ..." \
-H "Content-Type: application/json" \
-d '{"reason": "patient_request"}'
```
```json theme={null}
{
"appointment_uid": "pdm_123",
"status": "cancelled"
}
```
Patient cancellations honour the appointment type's `cancellation_min_notice_minutes`. If you cancel inside the notice window the API returns a `400` with the offending field; rebook the patient or call with an API key for an operational override.
**Cancelling a paid appointment refunds it in full.** If the appointment carries a payment
that is `paid`, `partially_refunded` or `refund_required`, cancelling it automatically
requests a refund of everything still refundable on that payment. Rebooking does **not** —
the patient keeps the appointment they paid for, at a new time.
The refund is requested, not completed, by the time this endpoint responds: it is sent to the
payment provider moments later and the appointment's refund record moves from `pending` to
`refunded`.
A partial refund made beforehand does **not** leave you holding the difference: cancelling
returns the *remainder*, so a €40.00 partial refund on a €49.00 payment followed by a
cancellation sends back the remaining €9.00 and the payer has had all €49.00. Retaining part of
a payment across a cancellation is not supported.
The RxScale platform fee is **not** returned with a refund. Your Mollie account sends the payer
the full amount and RxScale keeps its fee, so a full refund costs you that fee.
## Rebook Appointment
Move an existing appointment to a new slot. The rebook flow allocates a new appointment row (with `previous_meeting_uid` set to the original) and cancels the old one atomically.
```bash theme={null}
POST /v1/scheduling/appointments/{appointment_uid}/rebook
```
Unix seconds for the new slot.Required when the appointment type's `rebooking_mode` is `any_doctor`. For `same_doctor_only`, the original doctor is reused.Optional. Defaults to the original appointment type.Strongly recommended — protects against double-rebooks during retries.
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/scheduling/appointments/pdm_123/rebook" \
-H "X-RxScale-Booking-Token: eyJ..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: rebook-attempt-1" \
-d '{"new_start_date": 1778510000}'
```
```json theme={null}
{
"appointment_uid": "pdm_456",
"status": "confirmed"
}
```
Rebooks are blocked if the appointment type sets `allow_patient_rebooking: false`, if `rebooking_min_notice_minutes` has not been met, or if the new slot is taken.
## Get Join Token
Returns a short-lived Jitsi waiting-room JWT and the room name. Used by the patient hosted UI to launch the video call.
```bash theme={null}
POST /v1/scheduling/appointments/{appointment_uid}/join
```
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/scheduling/appointments/pdm_123/join" \
-H "X-RxScale-Booking-Token: eyJ..."
```
```json theme={null}
{
"room_name": "doctor-123-room",
"token": "eyJhbGciOi...",
"expires_at": 1778497200
}
```
### Constructing the patient meeting link
The response does **not** return a ready-made link. Your integration must build it:
```
https:///meeting/{room_name}?jwt={token}
```
You can optionally append a `return_url` query parameter to redirect the patient back to your
platform after the call ends:
```
https:///meeting/{room_name}?jwt={token}&return_url=https%3A%2F%2Fpartner.example%2Fafter-call
```
The `token` value returned by this endpoint. Signed by RxScale; do not modify it.
URL to redirect the patient to after the call ends. The redirect is honoured **only if** the
URL's origin (scheme + host + port) is on the organisation's admin-configured allow-list.
If the origin is not on the list, or if no `return_url` is supplied, the patient is simply
returned to the previous page. Configure the allow-list in the Admin Portal under
**Settings → Return URLs**.
This `return_url` is only for post-call navigation on the meeting page. It is distinct from
the `return_url` in the booking session body (used to redirect the patient after a successful
booking). Do not confuse them.
### Join window
The endpoint accepts join requests from **10 minutes before** the appointment `start_date` until **60 minutes after** the appointment `end_date`. Requests outside that window return:
```json theme={null}
{ "error": ["Appointment is outside the join window"] }
```
Surface this clearly in your hosted UI — the patient should see a countdown or "join opens in N minutes" hint rather than a blocked button.
## Hosted UI
Redirect patients to the `launch_url` returned by the booking session endpoint, or embed the widget script:
```html theme={null}
```
The widget renders the hosted booking page in an iframe inside a Shadow DOM wrapper so styles do not leak into the host page.
## Appointment Reminder Action Links
These endpoints are called by the RxScale-hosted meetings UI when a patient opens a **Join**,
**Reschedule**, or **Cancel** link from an appointment reminder email or SMS. Partners do not
call them directly — they are documented here for completeness.
Each reminder link embeds an opaque, single-use launch `code` minted by RxScale when the
reminder is sent. The code itself is the credential — no API key or patient booking token is
required or accepted on these routes.
### Get Action Link Context
Loads the display context the hosted UI needs to render the reminder landing page.
```bash theme={null}
GET /v1/scheduling/appointment-actions/{code}
```
No authentication header is needed — the code itself is the credential. A missing, expired, or
out-of-scope code returns `404`.
```bash theme={null}
curl "https://api.rxscale.com/v1/scheduling/appointment-actions/aac_Xy9..."
```
```json theme={null}
{
"action": "rebook",
"appointment_start": 1778490000,
"appointment_end": 1778490900,
"doctor_display_name": "Dr. Max Meyer",
"appointment_type_name": "Video consultation",
"join_window": {
"state": "too_early",
"opens_at": 1778489400
},
"rebook_allowed": true,
"cancel_allowed": true,
"already_actioned": false,
"doctor_uid": "doc_123",
"rebooking_mode": "same_doctor_only"
}
```
| Field | Type | Description |
| ----------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `action` | string | Which action this link launches: `join`, `rebook`, or `cancel` |
| `appointment_start` | integer | Appointment start time (Unix seconds) |
| `appointment_end` | integer | Appointment end time (Unix seconds) |
| `doctor_display_name` | string | Doctor display name, for the landing page header |
| `appointment_type_name` | string | Appointment type name |
| `join_window` | object | `{ state, opens_at }`. `state` is `too_early`, `open`, or `too_late`; `opens_at` is the Unix timestamp the room opens (10 minutes before `appointment_start`) |
| `rebook_allowed` | boolean | Whether rescheduling is currently allowed for this appointment |
| `cancel_allowed` | boolean | Whether cancelling is currently allowed for this appointment |
| `already_actioned` | boolean | Whether this link's action has already been completed (for example, the appointment was already cancelled through it) |
| `doctor_uid` | string | Only present when `action` is `rebook`. The current doctor's UID |
| `rebooking_mode` | string | Only present when `action` is `rebook`. Either `same_doctor_only` or `any_doctor_same_organisation` |
### Join
Returns the same waiting-room token shape as [Get Join Token](#get-join-token). Reusable while
the join window stays open.
```bash theme={null}
POST /v1/scheduling/appointment-actions/{code}/join
```
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/scheduling/appointment-actions/aac_Xy9.../join"
```
```json theme={null}
{
"room_name": "doctor-123-room",
"token": "eyJhbGciOi...",
"expires_at": 1778497200
}
```
Returns `400` if called outside the join window (10 minutes before `appointment_start` to 60
minutes after `appointment_end`).
### Cancel
```bash theme={null}
POST /v1/scheduling/appointment-actions/{code}/cancel
```
Optional free-text reason.
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/scheduling/appointment-actions/aac_Xy9.../cancel" \
-H "Content-Type: application/json" \
-d '{"reason": "patient_request"}'
```
```json theme={null}
{
"appointment_uid": "pdm_123",
"status": "cancelled"
}
```
Single-use: once the code has cancelled the appointment, calling it again returns `409`, as does
any call once the appointment is no longer cancellable (already completed, or outside the
cancellation notice window). This is the only one of these four endpoints that emits a
`patient_doctor_meeting_updated` event with `change: "cancelled"`.
### Start a Rebooking Session
Mints a `rebook`-mode hosted booking session so the patient can pick a new slot through the same
hosted booking flow described under [Create Booking Session](#create-booking-session).
```bash theme={null}
POST /v1/scheduling/appointment-actions/{code}/rebook-session
```
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/scheduling/appointment-actions/aac_Xy9.../rebook-session"
```
```json theme={null}
{
"booking_launch_code": "blc_Rk2...",
"expires_at": 1778458500,
"launch_url": "https://meet.rxscale.com/booking?launch=blc_Rk2..."
}
```
Idempotent — calling it again before the session expires returns the same
`booking_launch_code` rather than minting a new one, and it does not consume the reminder
action-link code. The original appointment stays active until the patient actually confirms a
new slot: only then is it cancelled and replaced, firing a `rebooked` event.
## Doctor Portal Endpoints
These endpoints are scoped to the authenticated doctor (Auth0 token), used by the RxScale Doctor
Portal. Partners typically don't need to call them directly.
### List Scheduled Appointments
```bash theme={null}
GET /v1/doctor/appointments
```
Query parameters mirror the admin list endpoint: `status`, `from`, `to`, `patient_uid`,
`page`, `limit`. The response is the doctor's own appointments only.
### Availability Rules CRUD
```bash theme={null}
GET /v1/doctor/availability-rules
POST /v1/doctor/availability-rules
PATCH /v1/doctor/availability-rules/{rule_uid}
DELETE /v1/doctor/availability-rules/{rule_uid}
```
Each rule has `weekday` (0 = Monday … 6 = Sunday), `start_time` and `end_time` in minutes since
midnight, an optional `buffer_minutes` between consecutive slots, and optional `valid_from` /
`valid_until` Unix-second bounds. PATCH bodies are partial; pass `clear_valid_from: true` or
`clear_valid_until: true` to drop a previously-set bound. DELETE is a soft delete.
## Admin Endpoints
These endpoints are scoped to the authenticated organisation admin (Auth0 token).
### List Appointments
```bash theme={null}
GET /v1/admin/scheduling/appointments
```
Query parameters: `doctor_uid`, `patient_uid`, `status`, `from`, `to`, `page`, `limit`. By default
returns active (held + confirmed) appointments scheduled from now onwards; pass `status=all` and
`from=0` to see history.
### Cancel Appointment
```bash theme={null}
POST /v1/admin/scheduling/appointments/{appointment_uid}/cancel
```
Requires `{"reason": "..."}` in the body.
### Appointment Type Reminders
```bash theme={null}
GET /v1/admin/scheduling/appointment-types/{appointment_type_uid}/reminders
POST /v1/admin/scheduling/appointment-types/{appointment_type_uid}/reminders
PATCH /v1/admin/scheduling/appointment-types/{appointment_type_uid}/reminders/{reminder_uid}
DELETE /v1/admin/scheduling/appointment-types/{appointment_type_uid}/reminders/{reminder_uid}
```
Each reminder has `recipient_role` (`patient` / `doctor` / `admin`), `minutes_before` (positive
integer up to 60 days), `send_email`, `send_sms`, and `active`. Multiple reminders per
`(appointment_type, recipient_role)` are allowed as long as the `minutes_before` offset differs.
A maintenance job runs every minute, finds confirmed appointments whose firing window includes
"now", and publishes one `scheduling.appointment_reminder_due` event per resolved recipient.
The published event always fires (partner webhook subscribers receive it); RxScale's own
notification handler only dispatches email/SMS when the corresponding `send_email` / `send_sms`
flag is set on the reminder row.
If the appointment has a `visit_reason`, the reminder event and RxScale email/SMS content include it.
Partners subscribe to the event via the existing organisation notification subscription endpoint
with `notification_type=APPOINTMENT_REMINDER_DUE`.
### View Doctor Availability
```bash theme={null}
GET /v1/admin/scheduling/doctors/{doctor_uid}/availability-rules
```
Read-only listing of a doctor's weekly bookable windows. Returns `404` if the doctor is not in
the admin's organisation.
### Booking-Token Secrets
```bash theme={null}
GET /v1/admin/scheduling/booking-token-secrets
GET /v1/admin/scheduling/booking-token-secrets/{key_id}
POST /v1/admin/scheduling/booking-token-secrets
DELETE /v1/admin/scheduling/booking-token-secrets/{key_id}
```
`POST` provisions a new secret and returns `{key_id, secret}`. `GET` returns the secret value
decrypted — RxScale stores secrets encrypted at rest, but admins can re-read them any time from
this endpoint (and from the admin portal Settings → Booking secrets page) so a new minter can be
configured without re-provisioning. `DELETE` revokes the secret.
The legacy organisation-scoped paths
(`/v1/admin/scheduling/organisations/{organisation_uid}/booking-token-secrets[/]`) are
still accepted and validate against the authenticated organisation.
## Errors
The Scheduling API uses standard HTTP status codes:
| Status | Meaning |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | Schema validation failed, or a business rule blocked the action (see the `error` body for the field/message). |
| `401` | Booking token or API key missing or invalid. |
| `404` | Resource not found, **or** the caller is not authorised to see it. The API returns `404` for unauthorised access to prevent resource enumeration. |
| `409` | Slot taken by another patient between hold and confirm. Re-search and try again. |
| `429` | Rate limit (10 requests per second per service). Back off and retry. |
# Authentication
Source: https://docs.rxscale.com/authentication
How to authenticate with RxScale APIs
# Authentication
All RxScale APIs use API key authentication. Include your API key in the `X-API-Key` header with every request.
## API Key Header
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/external_pharmacy_api/health/" \
-H "X-API-Key: your-api-key-here"
```
### Alternative Public API Headers
The Public API also accepts `X-RxScale-Authorization` as an alternative to `X-API-Key`. This is supported for backward compatibility with existing integrations.
```bash theme={null}
curl -X GET "https://api.rxscale.com/v2/public-api/health/" \
-H "X-RxScale-Authorization: your-api-key-here"
```
You can also send the same API key as a bearer token in the `Authorization` header.
```bash theme={null}
curl -X GET "https://api.rxscale.com/v2/public-api/health/" \
-H "Authorization: Bearer your-api-key-here"
```
New integrations should use `X-API-Key` unless your platform requires bearer-token
authentication. The `X-RxScale-Authorization` and `Authorization: Bearer` headers
are only supported on Public API endpoints.
## API Key Types
### Pharmacy API Keys
Pharmacy API keys can be scoped to:
* **A single pharmacy** — The key can only access data for that specific pharmacy.
* **A pharmacy group** — The key can access data for any pharmacy in the group. When using a group-wide key, you must include the `pharmacy_uid` query parameter to specify which pharmacy you are acting on.
### Management API Keys
Management API keys are scoped to an **organisation**. They can access data for all entities within that organisation.
## Permissions
Each API key has a set of permissions that control which endpoints it can access. Common permissions include:
| Permission | Description |
| ------------------- | ---------------------------- |
| `orders_read` | List and view orders |
| `orders_write` | Update order status |
| `stock_read` | List SKUs and stock levels |
| `stock_write` | Update stock levels |
| `prescription:read` | View prescription data |
| `product:read` | View product catalog |
| `webhooks_read` | List webhook subscriptions |
| `webhooks_write` | Register and manage webhooks |
## Creating API Keys
### As an Admin (Management API & Public API)
1. Log in to the **Admin Tool**
2. Navigate to **Settings** → **API Keys**
3. Click **Create API Key**
4. Enter a display name for the key
5. Select the permissions you want to grant (e.g. `order:read`, `product:read`, `prescription:read`)
6. Click **Create** — the key will be shown once. Copy and store it securely.
The API key is only shown once at creation time. If you lose it, you will need to create a new one.
### As a Pharmacist (External Pharmacy API)
1. Log in to the **Pharmacy Tool**
2. Navigate to **Settings** → **API Keys**
3. Create a new key scoped to your pharmacy or pharmacy group
4. Select the required permissions (e.g. `orders_read`, `orders_write`, `stock_read`)
Pharmacy API keys can be restricted to a single pharmacy or cover an entire pharmacy group. Group-wide keys require the `pharmacy_uid` parameter on each request.
## Error Responses
If authentication fails, you will receive one of these responses:
| Status Code | Description |
| ----------- | ------------------------------------------------------------ |
| `401` | Missing or invalid API key |
| `403` | Valid API key but insufficient permissions for this endpoint |
| `404` | Resource not found or not accessible with your key |
For security, RxScale returns `404 Not Found` instead of `403 Forbidden` when you try to access a resource outside your scope. This prevents resource enumeration.
# Error Handling
Source: https://docs.rxscale.com/error-handling
Understand API error responses, status codes, and error formats
# Error Handling
All RxScale APIs return consistent error responses. This page documents the error formats, HTTP status codes, and common error scenarios.
## HTTP Status Codes
| Status Code | Meaning | When It Occurs |
| ----------- | -------------------- | -------------------------------------------------------------------- |
| `200` | OK | Successful GET, PATCH, or POST request |
| `201` | Created | Resource successfully created |
| `204` | No Content | Resource successfully deleted |
| `400` | Bad Request | Validation error, missing parameters, or invalid request body |
| `401` | Unauthorized | Missing or invalid authentication (token or API key) |
| `403` | Forbidden | Valid authentication but insufficient permissions |
| `404` | Not Found | Resource does not exist or you lack access to it |
| `409` | Conflict | Resource already exists (duplicate) |
| `422` | Unprocessable Entity | Request is well-formed but contains invalid data (e.g., corrupt PDF) |
| `429` | Too Many Requests | Rate limit exceeded |
For security reasons, **unauthorized access returns `404` instead of `403`** on resource endpoints. This prevents attackers from discovering which resources exist. If you receive a `404`, verify both that the resource UID is correct and that your API key has the required permissions.
## Error Response Formats
RxScale APIs use three error response formats depending on the error type.
### Standard Error
Most errors return a simple error string:
```json theme={null}
{
"error": "Resource not found"
}
```
Common messages:
* `"Resource not found"` — Resource doesn't exist or you lack access
* `"Bad request"` — Unexpected server-side error (details logged internally)
* `"Missing required parameters: from and to"` — Specific missing parameter info
### Authentication Error
Authentication and authorization failures return a code and description:
```json theme={null}
{
"code": "authorization_header_missing",
"description": "Authorization header is expected"
}
```
| Code | Status | Description |
| ------------------------------ | ------ | --------------------------------------------------- |
| `authorization_header_missing` | 401 | No `Authorization` or `X-API-Key` header provided |
| `invalid_header` | 401 | Malformed authorization header or unparseable token |
| `token_expired` | 401 | JWT token has expired |
| `invalid_claims` | 401 | Token audience or issuer mismatch |
| `invalid_api_key` | 401 | API key not found or inactive |
| `api_key_missing` | 401 | `X-API-Key` header is expected |
| `permission_denied` | 403 | API key lacks required permission for this endpoint |
### Validation Error
Schema validation failures return field-level error details:
```json theme={null}
{
"error": {
"shop_uid": ["Missing data for required field."],
"quantity": ["Not a valid integer."]
}
}
```
Each key is the field name, and the value is an array of error messages for that field. Fix all listed fields and retry.
## Common Error Scenarios
**Request:**
```bash theme={null}
curl https://api.rxscale.com/v1/management/products
```
**Response:**
```json theme={null}
{
"code": "api_key_missing",
"description": "X-API-Key header is expected"
}
```
**Fix:** Include the `X-API-Key` header in your request.
**Request:** Trying to write with a read-only API key.
```json theme={null}
{
"code": "permission_denied",
"description": "API key lacks required permission: orders_write"
}
```
**Fix:** Check your API key's permissions. You may need to create a new key with the required permissions.
**Request:** Accessing a resource with an invalid UID or without access.
```json theme={null}
{
"error": "Resource not found"
}
```
**Fix:** Verify the resource UID is correct. If you're sure the UID is valid, check that your API key has permission to access the resource.
**Request:** Submitting an invalid request body.
```json theme={null}
{
"error": {
"patient_data": {
"first_name": ["Missing data for required field."],
"date_of_birth": ["Not a valid integer."]
}
}
}
```
**Fix:** Check each field listed in the error and provide valid values.
**Request:** Creating a resource that already exists.
```json theme={null}
{
"error": "Pharmacy SKU already exists"
}
```
**Fix:** The resource already exists. Use a GET request to retrieve it, or use PATCH to update it.
You've exceeded the rate limit. Wait before retrying. See [Rate Limits](/rate-limits) for details and best practices.
## Best Practices
The status code tells you the error category. Parse the response body for details only after checking the status.
A `404` can mean the resource doesn't exist OR you lack access. Don't assume which — verify your API key permissions alongside the resource UID.
When rate limited, wait and retry with increasing delays. Never retry immediately in a tight loop.
For debugging, log the entire response body including status code and headers. RxScale support may ask for these details.
# Calendar Sync
Source: https://docs.rxscale.com/for-doctors/calendar-sync
Connect your Google or Microsoft calendar so RxScale appointments appear there — and so your existing commitments block bookable slots
# Calendar Sync
Connect your personal **Google** or **Microsoft (Outlook)** calendar and RxScale will
automatically add your scheduled appointments to it — so your bookings live alongside
the rest of your day. Sync runs in **both directions**: RxScale also reads when you are
already busy, so patients cannot book a slot that clashes with something already in your
calendar.
**Your patients' data stays limited.** A synced event contains a generic title
(*"RxScale consultation"*), the appointment time, a link to your **Meetings** page
in the Doctor Portal, and the patient-provided visit reason when one was supplied.
Patient names and order information are not written to your external calendar.
**What RxScale reads from your calendar.** To block clashing slots, RxScale reads the
calendars you **own or can edit** (calendars merely shared with you, and subscribed
calendars such as holidays, are skipped). It stores only the **start and end time** of
each busy period, for the next **60 days** — never event titles, descriptions,
locations, attendees or organisers. Those times are used solely to hide slots you are
not free for, and are deleted as soon as you disconnect the calendar.
## Connecting a calendar
1. Open the **Meetings → Availability** page in the Doctor Portal.
2. In the **Calendars** section, click **Connect** next to Google or Microsoft.
3. Sign in to your calendar provider and grant access when prompted.
4. You are returned to RxScale; the calendar now shows as **Connected**.
**Where Google appointments appear.** For Google, RxScale creates a dedicated
calendar called **"RxScale Appointments"** in your account and adds your bookings
there (rather than to your primary calendar). It still shows alongside your other
events in Google Calendar, and you can hide or recolour it like any other calendar.
Microsoft (Outlook) appointments are added to your primary calendar.
After connecting, use **Sync meetings** on the calendar to add your existing
upcoming confirmed appointments. New bookings are added automatically from then on;
this button is only needed to backfill appointments confirmed before you connected.
You can press it again any time — it never duplicates events.
You can connect **both** a Google and a Microsoft calendar at the same time — each
appointment is added to every connected calendar.
## What gets synced
| When | What happens on your calendar |
| ------------------------------- | ---------------------------------- |
| An appointment is **confirmed** | A new event is created |
| An appointment is **rebooked** | The event is moved to the new time |
| An appointment is **cancelled** | The event is removed |
Only confirmed appointments are synced — pending holds are not.
## How your calendar blocks bookable slots
Anything marked busy in a connected calendar hides the overlapping slots from patients,
so you will not be double-booked. RxScale refreshes this regularly, and checks your
calendar once more at the moment a patient reserves a slot — so a meeting you accept
minutes earlier is still taken into account.
These events **do not** block your slots:
* Invitations you **declined**, and cancelled meetings.
* Events you marked **free** ("Show as free" in Google, *Free* in Outlook).
* **All-day** events and **tentative** / "maybe" invitations — an all-day entry such as a
conference or a birthday does not close your whole day.
* The RxScale appointments we added ourselves.
If you need an all-day entry to block bookings, add a normal timed event marked *busy*
for the hours you are unavailable, or block the time on your **Availability** page.
**Slots stay bookable up to 60 days ahead**, which is how far RxScale reads your
calendar. If your calendar shows **Reconnect needed**, RxScale can no longer read it —
your slots stay open, so reconnect promptly to avoid clashes.
**Google connections made before this feature need reconnecting.** Reading your busy
time needs a read permission that older connections were never asked for. Those show
**Reconnect needed** — click **Reconnect** and approve the extra permission.
## Disconnecting
Click **Disconnect** next to a connected provider. RxScale revokes its access,
removes any **upcoming** RxScale events it created from that calendar (past
appointments are left as a record), deletes the busy times it had stored for you,
and stops syncing. Your slots then depend on your RxScale availability alone.
If a connection shows **Reconnect needed**, your provider's access expired or was
revoked (for example, you changed your password). Syncing is paused — the
**Sync meetings** button is hidden, and your calendar no longer blocks slots —
until you click **Reconnect** to restore access.
# FAQ
Source: https://docs.rxscale.com/for-doctors/faq
Frequently asked questions for doctors using the RxScale Doctor Portal
# Frequently Asked Questions
Find answers to common questions about using the RxScale Doctor Portal.
## Prescriptions
There are a few reasons why a prescription might not appear in your queue:
* **Questionnaire assignment** -- Prescriptions are routed to doctors based on questionnaire assignments. If a questionnaire is not assigned to you, the associated prescriptions will not appear in your Anamnesis Center.
* **Blacklisted SKUs** -- Certain products (SKUs) may be excluded from your review queue based on your organisation's configuration. If a prescription contains only blacklisted SKUs, it will not appear for you.
* **Organisation scope** -- You can only see prescriptions for the organisation you are currently logged into. If you work with multiple organisations, make sure you are viewing the correct one.
* **Already reviewed** -- Another doctor in your organisation may have already reviewed and acted on the prescription.
If you believe you should be seeing prescriptions that are not appearing, contact your organisation's administrator.
When you place a prescription on hold, it means you need more information before you can make a decision. The prescription is temporarily paused and the support team is notified. They will follow up to gather the additional information needed. Once the situation is resolved, the prescription will return to the review queue.
Common reasons for placing a prescription on hold include:
* Incomplete or unclear questionnaire responses from the patient.
* A need for additional medical documentation.
* Questions about the patient's medical history that require clarification.
Once a prescription has been approved or declined, the decision is recorded and the prescription moves to the next stage. If you need to reverse a decision, contact the support team. They can assist with exceptional cases.
Patients are waiting for their medication, so timely reviews are important. Your organisation may have guidelines about expected review times. In general, reviewing prescriptions promptly throughout the day helps keep the workflow moving smoothly.
Approved prescriptions will remain in your Sign Center queue until they are signed. Signing requests have an expiration window, so if you wait too long, the signing request may expire and a new one will need to be created. It is best to sign prescriptions soon after approval.
## Account and Access
If you are associated with multiple organisations, you can switch between them from the dashboard. Look for the organisation selector, which is typically located in the sidebar or header area. Selecting a different organisation will update the portal to show prescriptions, patients, and settings for that organisation.
On the login page, click the "Forgot password" link. You will receive an email with instructions to reset your password. If you do not receive the email, check your spam folder or contact your organisation's administrator.
Yes, you can log in from any device with a modern web browser. However, for the best experience -- especially for video meetings -- we recommend using a desktop or laptop computer.
For technical issues, contact your organisation's administrator first. They can escalate the issue to the RxScale support team if needed.
## Patients
Patient records are typically created when a patient places an order through the system. In some cases, you may be able to create a direct prescription for a patient, which will also create or update their record. Contact your administrator for details about your organisation's patient management setup.
Navigate to the Patients section, find the patient using the search bar, and open their profile. Their prescription history is displayed in the patient detail view, showing all past prescriptions and their statuses.
# Getting Started
Source: https://docs.rxscale.com/for-doctors/getting-started
How to log in, set up your account, and navigate the RxScale Doctor Portal
# Getting Started
This page walks you through logging into the Doctor Portal for the first time, setting up your account, and finding your way around the dashboard.
## Logging In
The Doctor Portal uses Auth0 for secure authentication. To log in:
Navigate to the Doctor Portal URL provided by your organisation. This is typically a link shared by your administrator.
Enter the email address and password associated with your RxScale account. If your organisation uses single sign-on (SSO), you may be redirected to your organisation's login page.
If two-factor authentication is enabled for your account, follow the prompts to verify your identity.
After successful authentication, you will be taken to the Doctor Portal dashboard.
If you have not received login credentials, contact your organisation's administrator or your RxScale account manager.
## First-Time Setup
When you log in for the first time, take a moment to:
1. **Check your profile.** Make sure your name and contact information are correct. Your name will appear on signed prescriptions.
2. **Review your organisation settings.** Confirm that you are associated with the correct organisation. If you work with multiple organisations, you can switch between them from the dashboard.
3. **Familiarise yourself with the navigation.** Spend a few minutes exploring the sidebar to understand where everything is.
## Navigating the Dashboard
The Doctor Portal is organised around a sidebar with the following sections:
| Section | What It Does |
| ----------------- | --------------------------------------------------------------------------------------- |
| **Home** | Your main dashboard. Shows an overview of your pending tasks and recent activity. |
| **Prescriptions** | View and manage all prescriptions assigned to you or your organisation. |
| **Patients** | Search and view patient records, history, and details. |
| **Sign Center** | Sign approved prescriptions electronically using qualified electronic signatures (QES). |
| **Meeting** | Join video consultations with patients. |
### Home
The Home screen gives you a quick snapshot of what needs your attention. You can see how many prescriptions are waiting for review and how many are ready for signing.
### Prescriptions
The Prescriptions section lets you browse all prescriptions. You can filter by status to find prescriptions that are waiting for review, already approved, signed, or declined.
### Patients
The Patients section provides a searchable list of all patients in your organisation. You can view individual patient records, including their prescription history and any tags that have been applied.
### Sign Center
The Sign Center is where you go to electronically sign approved prescriptions. Prescriptions that have been approved but not yet signed will appear here. You can sign them individually or in batches.
### Meeting
The Meeting section lets you join video consultations with patients. You can select a meeting room and connect with patients for on-demand consultations.
## Next Steps
Now that you know your way around, learn how to:
* [Review prescriptions](/for-doctors/reviewing-prescriptions) in the Anamnesis Center
* [Sign prescriptions](/for-doctors/signing-prescriptions) in the Sign Center
* [Manage patients](/for-doctors/managing-patients) and their records
# Managing Patients
Source: https://docs.rxscale.com/for-doctors/managing-patients
How to view patient records, search patients, use tags, and create direct prescriptions
# Managing Patients
The Patients section of the Doctor Portal gives you access to your patient list, their medical history, and tools for organising patient records.
## Patient List and Search
The Patients section shows a list of all patients associated with your organisation. You can search for patients by name or other identifying information to quickly find the person you are looking for.
Use the search bar at the top of the patient list to filter results. As you type, the list narrows down to show matching patients.
## Viewing Patient Details
When you select a patient from the list, you can view their detailed profile, which includes:
* **Personal information** -- Name, date of birth, and contact details.
* **Prescription history** -- A list of all prescriptions associated with this patient, including their current status (approved, signed, declined, etc.).
* **Questionnaire responses** -- Past anamnesis questionnaire responses submitted by the patient.
* **Notes** -- Any notes that have been added to the patient's record by doctors or the support team.
This information helps you make informed decisions when reviewing new prescription requests from the same patient.
## Patient Tags
Tags let you organise and categorise patients. You can add tags to patient records to mark important information or group patients by specific criteria. For example, you might tag patients who need follow-up consultations or who have specific medical conditions relevant to your practice.
Tags are visible to other doctors and staff in your organisation, so they can be a useful way to share information across your team.
## Direct Prescriptions
In some cases, you may need to create a prescription for a patient directly, without the patient going through the standard order and questionnaire process. The Doctor Portal allows you to create direct prescriptions from a patient's profile.
When creating a direct prescription:
1. Navigate to the patient's profile.
2. Select the option to create a new prescription.
3. Choose the medication and dosage.
4. The prescription will be created and will appear in your Sign Center for electronic signing.
Direct prescriptions follow the same signing and fulfillment process as prescriptions created through the standard order flow. They still require your electronic signature before they can be sent to a pharmacy.
## Tips
* **Keep patient records up to date.** Accurate records help you and your colleagues make better clinical decisions.
* **Use tags consistently.** Agree on tag conventions within your organisation so that tags are meaningful and useful for everyone.
* **Review patient history before prescribing.** Always check a patient's prescription history before approving a new prescription to avoid duplicates or interactions.
## Related Topics
* [Reviewing Prescriptions](/for-doctors/reviewing-prescriptions) -- How to review prescription requests.
* [Signing Prescriptions](/for-doctors/signing-prescriptions) -- How to sign approved prescriptions.
# Doctor Portal
Source: https://docs.rxscale.com/for-doctors/overview
Your guide to using the RxScale Doctor Portal
# Welcome to the RxScale Doctor Portal
This guide helps you get the most out of the RxScale Doctor Portal. Whether you are reviewing prescriptions, signing documents, or managing patients, you will find clear instructions here to help you through every step.
## What Can You Do in the Doctor Portal?
The Doctor Portal is your central workspace for managing prescriptions and patient care within the RxScale platform. Here is what you can do:
Review incoming prescription requests in the Anamnesis Center. Approve, decline, or place prescriptions on hold.
Electronically sign approved prescriptions in the Sign Center using qualified electronic signatures.
View patient details, search your patient list, manage tags, and create direct prescriptions.
Join video consultations with patients directly from the portal.
## Getting Started
If this is your first time using the Doctor Portal, start with the [Getting Started](/for-doctors/getting-started) guide to learn how to log in, set up your account, and navigate the dashboard.
## Help Center
For detailed explanations of statuses, workflows, and platform concepts, visit the [Help Center](/help-center/overview):
* [Order Statuses](/help-center/order-statuses) — What each order status means
* [Prescription Statuses](/help-center/prescription-statuses) — Review and signing workflow stages
* [Signing Process](/help-center/signing) — How qualified electronic signatures work
* [Delivery Types](/help-center/delivery-types) — Available delivery options
* [Wallet Passes](/help-center/wallet-passes) — Patient identity verification
* [Notifications](/help-center/notifications) — How webhook notifications work
## Need Help?
If you have questions that are not covered in this guide, contact your RxScale account manager or reach out to our support team. You can also check the [FAQ](/for-doctors/faq) for answers to common questions.
# Reviewing Prescriptions
Source: https://docs.rxscale.com/for-doctors/reviewing-prescriptions
How to review, approve, decline, and manage prescriptions in the Anamnesis Center
# Reviewing Prescriptions
The Anamnesis Center is where you review prescription requests from patients. This page explains how the review process works and what actions you can take.
## The Anamnesis Center
The Anamnesis Center is your main workspace for reviewing prescriptions. When patients submit orders that require a prescription, those requests arrive here for your review.
Prescriptions appear in a queue and are presented to you one by one (or grouped by order, depending on your organisation's configuration). This focused approach helps you give each prescription the attention it deserves without being overwhelmed by a long list.
## What You See During Review
When you open a prescription for review, you will see:
* **Patient information** -- The patient's name, date of birth, and relevant medical details.
* **Questionnaire responses** -- The patient's answers to the medical questionnaire (anamnesis) associated with the requested medication.
* **Requested medication** -- The specific product and dosage the patient is requesting.
* **Patient history** -- Previous prescriptions and any relevant notes from past reviews.
* **Patient warnings** -- If an RxScale administrator has flagged the patient, a coloured warning banner appears at the top of the review.
Patient warnings are set by RxScale administrators -- you cannot edit them. They have a severity (informational, warning, or critical). A **critical** warning must be acknowledged before you continue the review, and you are prompted again each time you open the review.
## Actions You Can Take
After reviewing a prescription, you have three options:
### Approve
If the prescription is medically appropriate based on the patient's information and questionnaire responses, approve it. The prescription will then move to the Sign Center, where it needs to be electronically signed before it can be sent to a pharmacy.
### Decline
If the prescription is not appropriate, decline it. When you decline a prescription, you will be asked to provide a reason. This note is important because:
* It helps the patient understand why their request was not approved.
* It creates a record of your clinical decision.
* It may be shared with the patient or the support team.
Always include a clear and professional reason when declining a prescription. This is a required step.
### On Hold
If you need more information before making a decision, you can place the prescription on hold. When placing a prescription on hold, add a note explaining what additional information is needed. The support team will follow up and the prescription will return to your queue once the situation is resolved.
## What Happens After Your Decision
| Action | What Happens Next |
| ----------- | --------------------------------------------------------------------------------------------------------------------- |
| **Approve** | The prescription moves to the Sign Center. It will appear in your signing queue, ready for your electronic signature. |
| **Decline** | The prescription is marked as declined. The patient is notified and the associated order is updated. |
| **On Hold** | The prescription is placed on hold for the support team to review. It will return to the queue once resolved. |
## Tips for Efficient Review
* **Review prescriptions regularly.** Patients are waiting for their medication, so timely reviews make a real difference.
* **Read the questionnaire responses carefully.** The patient's answers to the anamnesis questionnaire are your primary source of clinical information.
* **Use clear notes.** Whether you are declining or placing a prescription on hold, write notes that are specific and actionable.
## Reassigning a Prescription to Another Doctor
If a case is better handled by a colleague, you can reassign a prescription without declining it. Reassignment is available while reviewing a prescription in the Anamnesis Center and from the patient detail page.
You can only reassign prescriptions that have not yet been signed — those that are awaiting review, on hold, or approved but not yet signed. Already-signed or declined prescriptions cannot be reassigned.
To reassign a prescription, select **Assign to another doctor** from the prescription actions. A picker appears listing your colleagues. Colleagues who cannot take this prescription are still shown, but greyed out and disabled with the reason — either they have not been enabled by an administrator to receive assigned prescriptions, or they are blacklisted for one of the prescription's products. You can only select an eligible colleague.
After you confirm the reassignment:
* The prescription moves back to "waiting for doctor" status for the new doctor.
* The change is recorded in the prescription's history so there is a clear audit trail.
If every colleague is greyed out, contact your administrator to enable the **Can Receive Assigned Prescriptions** setting for the relevant doctor accounts, or to review the product blacklist for those doctors.
## Related Topics
* [Signing Prescriptions](/for-doctors/signing-prescriptions) -- Learn how to sign approved prescriptions.
* [Prescription Statuses](/help-center/prescription-statuses) -- See all prescription statuses and their meanings.
# Scheduled appointments
Source: https://docs.rxscale.com/for-doctors/scheduled-appointments
How scheduled video appointments work from the doctor's perspective
# Scheduled appointments
When your organisation enables patient self-booking, patients pick a time slot directly from a
hosted booking page. The appointment is held in your calendar as soon as they confirm. This page
explains how scheduled appointments work from your perspective.
## How an appointment reaches your calendar
1. The patient lands on the hosted booking page (linked from your partner's site) and chooses a
time slot during one of your published availability windows.
2. Your slot is briefly held (typically 15 minutes) while the patient completes the booking flow.
3. The patient confirms. The appointment moves to `confirmed` and is reserved for you.
4. If the patient does not confirm in time, the hold expires and the slot becomes bookable again.
## Where to see your appointments
Open the **Appointments** entry in the sidebar to see every appointment scheduled with you. The
default view shows your upcoming confirmed appointments; you can filter by status (confirmed,
cancelled, expired, completed, no-show) or by date range to dig into history. Slots that a patient
is still in the middle of booking are never shown — only confirmed appointments reach your list.
Each row has an **Open patient** shortcut that jumps straight to the patient's record. The patient
record also includes a **Scheduled appointments** card so you can review that patient's appointment
history and record an outcome without leaving the detail screen. Use the table headers to sort by
appointment type, status, start time, or end time.
If the patient entered a reason while booking, it appears in the appointment list and the patient's
scheduled appointments card.
Your **home dashboard** also shows a **Today's appointments** card listing the appointments still
to come today. Confirmed appointments on that card include a **Join** action, and the top bar shows
a live countdown to your next appointment — click it to open that patient.
## Joining the video call
Each scheduled appointment maps to a persistent meeting room linked to you. You always land in
the same room for the same patient on the same day, regardless of the booking flow they used.
Use the **Meeting** entry in the sidebar to enter your persistent room. Cross-reference your
upcoming **Appointments** list to see who you're meeting next.
Click the join button. Your browser will request camera and microphone access.
The patient joins through their own join link. If they're not there yet, you'll see the
waiting room.
### Join window
You can open the room from **10 minutes before** the appointment starts until **60 minutes after**
the scheduled end time. Outside that window the join action is disabled.
This window covers normal cases:
* Patient arrives early → you join early and wait.
* Consultation overruns slightly → you stay in the room past the scheduled end.
* Patient is late by up to an hour → you can still meet without rescheduling.
For longer delays, ask the patient to rebook or coordinate with support.
## Cancellations
You can cancel an appointment from its detail page when something prevents the consultation from
happening (you're unavailable, the patient asks to cancel, etc.). A reason is required so support
and the patient understand what happened.
If the patient cancels from their side, the appointment status changes to `cancelled` and it
drops out of your active list.
## Emails about confirmations, cancellations, and reschedules
Your organisation can have RxScale email people when a scheduled appointment is confirmed,
cancelled, or moved to a new time. It stays off unless your organisation switches it on, and
patients, doctors, and admins are configured separately.
* **Patients** get a short email naming the appointment type, you, and the time. A confirmation
arrives once the booking is confirmed; a cancellation includes the reason if one was given; a
reschedule gives the new time.
* **You** get the same messages about your own appointments, if your organisation enabled doctor
emails — including the confirmation email when one of your appointments is freshly booked.
* **Admins** — if your organisation subscribes admins, every user in the organisation with an
email address receives the same messages, independent of your own settings.
Moving an appointment sends **one** email — the reschedule — never a cancellation followed by a
new booking. A confirmation is unrelated to that: it fires once, when a fresh booking is
confirmed, and a later reschedule of that appointment does not trigger a second confirmation.
### Turning off your own copy
Open **Settings** in the sidebar, find **Appointment update emails**, and switch off **Email me
about cancellations and reschedules**. It is on by default, so you only need to visit it to opt
out. The same toggle also silences your confirmation emails.
This affects your email only. **Emails to your patients and to admins keep going out**, and the
appointment itself is unchanged either way. Whether doctors are emailed at all is your
organisation's setting — if you receive nothing while the toggle is on, ask your admin.
## Recording the outcome
After a consultation you record how it went so the appointment doesn't stay open. When you leave
the video call, you're prompted to mark the appointment as **Completed** or **No-show** and add
optional notes. The notes are saved to the patient's meeting history; mark a note **internal** to
keep it from being shared with the patient.
If you skip the prompt or close the call window without leaving the call cleanly, the appointment
keeps a **Needs outcome** flag on your appointments list with a **Record outcome** action so you
can set it later. You can also correct an outcome you already recorded (for example switch a
no-show back to completed) using the same action.
Use **No-show** when the patient does not join within the join window — it's used for billing and
reporting and is distinct from a cancellation.
## Availability windows
Your bookable hours live under the **Availability** entry in the sidebar. Each window is a
recurring weekly slot (e.g. Monday 09:00 – 11:00) during which patients can book you. From the
availability page you can:
* **Add a window.** Choose a day, a start time and end time (entered as a time of day, e.g. 09:00),
and an optional buffer between consecutive slots.
* **Edit a window.** Change its times or toggle it off without deleting — useful for temporary
pauses.
* **Remove a window.** Soft-deletes the rule; patients will stop seeing slots in that block.
Validity periods (`valid_from` / `valid_until`) can be applied when you need a rule to apply only
for a date range; contact support if you need help wiring those up.
### Date overrides (specific dates)
Recurring windows cover your normal week, but some days are different. Under **Specific date
schedules** on the same availability page you can override a single calendar date without touching
your weekly rules:
* **Custom hours for a date.** Pick the date and set a start/end time (and optional buffer). On that
date, only these hours are bookable — your recurring windows for that weekday are ignored.
* **Mark a day off.** Pick the date and toggle **Day off**. That date becomes fully unavailable, even
if a recurring window normally applies.
* **Edit or remove an override.** Removing it returns the date to your recurring availability.
You can add more than one window to the same date (e.g. a morning and an afternoon block) by adding
two overrides for that date.
## Frequently asked questions
Slots that a patient is still booking are briefly held (status `held`) and are never shown in
your appointments. Once the patient confirms, the appointment appears as `confirmed`. If they
don't confirm in time, the hold expires and the slot returns to your availability.
The hold + confirm flow prevents this in normal operation. If you ever see it, contact support
— they can re-allocate one of the patients without losing the booking history.
Yes — the cancellation reason is shown on the appointment detail page.
## Related Topics
* [Video Meetings](/for-doctors/video-meetings) — General information on running video consultations.
* [Getting Started](/for-doctors/getting-started) — Doctor portal basics.
# Signing Prescriptions
Source: https://docs.rxscale.com/for-doctors/signing-prescriptions
How to electronically sign prescriptions in the Sign Center
# Signing Prescriptions
After you approve a prescription, it needs to be electronically signed before it can be sent to a pharmacy. The Sign Center is where you do this.
## The Sign Center
The Sign Center shows all prescriptions that have been approved and are waiting for your electronic signature. You can see how many prescriptions are in your signing queue from the counter displayed in the Sign Center.
## How Signing Works
RxScale uses **RxScaleSign** as the signing provider for qualified electronic signatures (QES). A QES has the same legal validity as a handwritten signature and is required for prescriptions to be legally valid in Germany and the EU.
Navigate to the Sign Center from the sidebar. You will see a count of prescriptions waiting to be signed.
The Sign Center shows the prescriptions that are ready for signing. You can review the details of each prescription before signing.
You can sign prescriptions individually or use batch signing to sign multiple prescriptions at once. Batch signing saves you time when there are several prescriptions waiting.
When you initiate the signing process, you will be prompted to authenticate through RxScaleSign. This ensures that only you can apply your signature.
Once signed, the prescription status changes to "signed". The prescription is now legally valid and the associated order moves forward to a pharmacy.
## Batch Signing
Batch signing lets you sign multiple prescriptions in a single session. This is especially useful when you have reviewed and approved several prescriptions and want to sign them all at once, rather than going through the signing process for each one individually.
When you use batch signing:
1. The Sign Center groups eligible prescriptions together.
2. You authenticate once through RxScaleSign.
3. All selected prescriptions are signed in one batch.
4. Each prescription receives its own individual qualified electronic signature.
Batch signing can significantly speed up your workflow. If you review prescriptions throughout the day, consider signing them in batches at set times.
### Optional Batch Review Before RxScaleSign
Doctors can enable an additional review step before starting a batch with RxScaleSign. When enabled, the Sign Center shows every prescription that will be included in the next batch together with the associated questionnaire information.
The doctor can remove individual prescriptions before signing. Each removed prescription requires its own comment and is returned for follow-up review. After the doctor confirms the batch, the remaining prescriptions continue to RxScaleSign.
While the setting is enabled, the review step always opens before a batch starts — including when signing is started from a patient's record, or from a Sign Center tab that was already open when the setting was turned on.
## Queue Counter
The Sign Center displays a counter showing how many prescriptions are waiting for your signature. This helps you keep track of your signing workload at a glance without needing to open the Sign Center each time.
The same number also appears as a badge on the Sign Center icon in the sidebar, so you can see how many prescriptions are ready to sign from any page. The badge disappears when the queue is empty and updates automatically while you work.
## What Happens After Signing
Once a prescription is signed:
1. The prescription status changes to **signed**.
2. The associated order status moves to **waiting for pharmacy**.
3. The order is routed to an appropriate pharmacy for fulfillment.
4. The patient and pharmacy are notified.
## Downloading Signed Prescriptions
You can download signed prescriptions as a ZIP file. This is useful for record-keeping or if you need to share a copy of the signed prescription. The downloaded file contains the prescription document with your qualified electronic signature.
## Frequently Asked Questions
Signing requests have an expiration window. If a signing request expires before you sign it, a new request may be created automatically. It is best to sign prescriptions promptly after approval.
Once a prescription is signed with a qualified electronic signature, it cannot be unsigned. If there is an issue with a signed prescription, contact the support team.
If you encounter issues during the RxScaleSign authentication step, try again. If the problem persists, contact the support team for assistance.
## Related Topics
* [Reviewing Prescriptions](/for-doctors/reviewing-prescriptions) -- Learn how to review prescriptions before signing.
* [Prescription Signing](/help-center/signing) -- Technical details about the electronic signing process.
* [Prescription Statuses](/help-center/prescription-statuses) -- See all prescription statuses and their meanings.
# Video Meetings
Source: https://docs.rxscale.com/for-doctors/video-meetings
How to join video consultations with patients
# Video Meetings
The Doctor Portal includes a video meeting feature that lets you conduct consultations with patients remotely. This page explains how to join meetings and manage your availability.
## Joining a Video Meeting
When a patient is scheduled for a video consultation, you can join the meeting directly from the Doctor Portal.
Navigate to the Meeting section from the sidebar.
Choose the meeting room you want to join. Meeting rooms may be organised by your organisation based on specialisation or availability.
Click to join the meeting. Your browser will request access to your camera and microphone. Allow these permissions to proceed.
You are now in the video call with the patient. Conduct your consultation as you would in person.
## Meeting Room Selection
Your organisation may have multiple meeting rooms configured. Each room can serve a different purpose, such as:
* **General consultations** -- For standard patient appointments.
* **Follow-up consultations** -- For patients who need additional discussion after an initial review.
* **Specialist rooms** -- For consultations related to specific medical areas.
Check with your organisation's administrator to understand how meeting rooms are set up for your team.
## On-Demand Consultations
In addition to scheduled meetings, the Doctor Portal supports on-demand consultations. This means patients can request an immediate video call, and available doctors can pick up the consultation.
When an on-demand consultation request comes in:
1. You will see a notification in the Meeting section.
2. You can choose to accept the consultation.
3. Once accepted, the video call begins immediately.
On-demand consultations work best when your organisation has clear guidelines about doctor availability and response times.
## Technical Requirements
For the best video consultation experience:
* Use a modern web browser (Chrome, Firefox, Edge, or Safari).
* Ensure you have a stable internet connection.
* Use a headset or earphones to reduce echo.
* Make sure your camera and microphone permissions are enabled in your browser settings.
## Frequently Asked Questions
If a patient is having trouble connecting, they can try refreshing their browser or switching to a different browser. If the issue persists, contact the support team for assistance.
Recording policies depend on your organisation's settings and local regulations. Check with your administrator for details.
The Doctor Portal is optimised for desktop browsers. While it may work on mobile devices, we recommend using a desktop or laptop computer for the best experience.
## Related Topics
* [Getting Started](/for-doctors/getting-started) -- Learn how to navigate the Doctor Portal.
# Booking your appointment
Source: https://docs.rxscale.com/for-patients/booking
Pick a time slot for your video consultation
# Booking your appointment
When your provider sends you a booking link, you land on a hosted RxScale booking page. The page
shows the available appointment types and time slots for the doctor or doctors who can see you.
Some providers also publish one link publicly — on their website, a doctor's bio, or an email
signature — instead of sending you a personal one. Anyone can use it to book. If that's how you got
here, there's one extra step before you reach the booking page: confirming your email address.
## Starting from a public booking link
We ask for this first so we know the details you add next are really yours — nothing is saved
to a patient record until you've confirmed you own that inbox.
We send you a link to continue. For your security it's only valid for a limited time — if it
has expired by the time you open it, just go back to the public link and enter your email
again.
If you've booked with this provider before, we already have your details on file and take you
straight to picking a time. Booking with them for the first time? We'll ask for whatever your
provider needs on file before they can prescribe — often your first name, last name and date of
birth, sometimes a little more. You'll only ever be asked for details we don't already have,
and some providers ask for nothing beyond your email address.
From here you're in the same flow as anyone with a personal link: pick a slot and confirm it.
Bookings made through a public booking link are never priced, so there's no payment step —
you simply confirm.
## Steps
Click the link your provider sent — or, if you started from a public link, continue from the
link in your confirmation email. The page loads with your appointment type already selected.
Before choosing a time, you can optionally explain why you are booking the appointment. Your
doctor and provider admins can see this reason, and it may appear in appointment reminders or
calendar invites.
Choose a date and time that works for you. Slots that are no longer available won't show up.
Once you've selected a slot, confirm it. You have a short window (usually 15 minutes) to
finish — after that, the slot returns to availability for other patients.
If your provider has priced this appointment, the page shows what you'll be charged — each
item, its price, and the total — and the button pays instead of confirming. You're taken to a
secure checkout page to pay, and your slot stays reserved while you're there. Your appointment
is confirmed as soon as the payment goes through. If your booking link carries no price,
nothing changes: you simply confirm.
After confirmation, you'll be redirected back to your provider's site (or shown a success page)
with your appointment time and the join link.
## Rebooking
If you need to move your appointment to a different time, look for the **Reschedule** link in
your appointment reminder email or text message, or the rebook option on your provider's site.
It opens the same booking flow you used originally so you can pick a new slot — depending on
your provider's rules, you may need to keep the same doctor. Whether rebooking is allowed — and
how close to the appointment you can do it — depends on your provider's rules.
If rebooking isn't available, contact your provider directly to reschedule.
## Cancelling
You can cancel your appointment from your provider's site, or via the **Cancel** link in your
appointment reminder email or text message. You'll be asked to confirm, with the option to add a
short reason — it helps your provider's support team understand what happened, but isn't always
required.
Most providers require a minimum notice period to cancel without charge. The booking page or your
provider's policy will tell you the cut-off.
**If you paid for the appointment, cancelling refunds you.** The full amount you have not already
had back is returned to the payment method you used. You don't need to ask for it. Refunds are
sent straight away, but how long they take to appear depends on your bank or payment method —
usually a few working days.
Rescheduling is different: moving your appointment to a new time keeps the payment against the
same booking, so you are neither charged again nor refunded.
## Emails about your booking
Some providers have us email you when your booking is confirmed, cancelled, or moved. If yours
does, the email goes to the address they already have for you and names the appointment, the
doctor, and the time — a confirmation email arrives once your booking is confirmed, a cancellation
includes the reason if one was given, and a reschedule tells you the new time. Moving an
appointment sends one email about the new time, not a cancellation followed by a booking.
Not every provider switches these on, so don't treat email as your only confirmation — if you're
not sure a booking went through or a change was applied, check with your provider.
## Frequently asked questions
Slots are held briefly while you confirm. If you took longer than the hold allows, another
patient may have taken the slot. Pick a new time and try again.
Check your spam folder first. If it's still missing, contact your provider — they can resend
the confirmation or check whether the booking went through.
Check your spam folder first, and double-check you typed your email address correctly. If
it's still missing, go back to the public link and enter your email again.
It's only valid for a limited time, and only once. Go back to the public link, enter your
email address again, and we'll send you a fresh one.
Yes — the booking page works on phones and tablets. For the video call itself, a laptop or
desktop with a good webcam usually works best.
No money was taken, and your slot is still reserved for you for the rest of the hold window.
Just try paying again from the same page. If the reservation ran out in the meantime, the page
tells you and takes you back to pick a new time.
Some payment methods take longer to settle than others. Nothing has gone wrong — you don't need
to pay again. Your provider will email you as soon as the appointment is confirmed.
This is rare: it happens when someone else takes the last slot while your payment is still
being processed. Your payment is refunded in full and you don't need to request it. Choose a
new time to book again, and contact your provider if you have any questions about the refund.
Your provider sends the refund straight away — usually within minutes of the cancellation.
After that it is up to your bank or payment method, which typically takes a few working days
to show it. If it has been longer than a week, contact your provider with the date you
cancelled.
Providers can refund part of a payment — for example when they have agreed a late-cancellation
fee with you, or when only part of what you booked is being cancelled. The reason your provider
gave may be shown on your bank statement alongside the refund. Contact them if it isn't clear.
# Joining your appointment
Source: https://docs.rxscale.com/for-patients/joining
Open the video room and meet your doctor
# Joining your appointment
When it's time for your appointment, open the join link your provider sent you. The link opens
the secure video room directly — no app install or separate account is needed.
Your appointment reminder email or text message includes this **Join** link directly, so you can
open the video room straight from the reminder without hunting for it elsewhere. If your
appointment can be rescheduled or cancelled, the same reminder also includes **Reschedule** and
**Cancel** links — see [Booking your appointment](/for-patients/booking) for how those work.
## When can I join?
You can open the room from **10 minutes before** your scheduled start time until **60 minutes
after** your scheduled end time. If you arrive earlier, you'll see a message telling you when
the room will open.
If you're more than an hour late, the link won't open the room. Contact your provider to
rebook or join through a new link.
## Steps
Click the link in your appointment email, your provider's site, or the booking confirmation
page.
Your browser will ask permission once. Click "Allow" so the doctor can see and hear you.
If your doctor hasn't joined yet, you'll see a waiting room. The call starts automatically
when they arrive.
The consultation runs like any other video call. When you're done, close the tab to leave.
After the call ends, you may be automatically redirected back to your provider's site.
If no redirect happens, simply close the tab or navigate away as normal.
## What you'll need
* A modern web browser (Chrome, Firefox, Edge, or Safari) — keep it up to date.
* A working camera and microphone.
* A stable internet connection. Wi-Fi or wired both work; mobile data may be choppy.
* A quiet, private space.
## Troubleshooting
You're either too early or too late. The room opens 10 minutes before the appointment and
stays open for an hour after. Check the time and try again.
Look for the camera icon in your browser's address bar — it usually lets you toggle the
permission. If your camera is in use by another app (Zoom, Teams), close that app and
refresh the page.
Move closer to your Wi-Fi router, or switch to a wired connection. Closing other tabs and
apps that use the network can also help.
Check your email, your provider's site, or your booking confirmation page. If it's still
missing, contact your provider — they can resend it.
# Overview
Source: https://docs.rxscale.com/for-patients/overview
Booking and joining your video appointment with RxScale
# Booking and joining your appointment
RxScale powers the video appointment booking your healthcare provider offers. This guide explains
what to expect when you book a video consultation and how to join your meeting.
You will not need to create a separate RxScale account. The booking page and the video room are
launched from your provider's site using a short-lived link they generate for you.
Pick a time slot and confirm your appointment.
Open the video room when it's time to meet your doctor.
## What you'll need
* A modern web browser (Chrome, Firefox, Edge, or Safari).
* A stable internet connection.
* Camera and microphone access — your browser will ask once you join the call.
* The booking link sent by your provider (usually in an email, SMS, or order confirmation page).
## How your data is handled
Your name, date of birth, and any other profile details are sent securely from your provider to
RxScale when you start the booking flow. The booking link itself does not expose your identity —
even if you forward it accidentally, the link can only be used to view the booking session, not
your personal data.
For full details on how RxScale handles patient data, ask your provider for their privacy notice.
# Wallet Pass at a pharmacy
Source: https://docs.rxscale.com/for-patients/wallet-pass-at-pharmacy
Continue securely from a pharmacy tablet on your own phone
# Wallet Pass at a pharmacy
Some pharmacies offer a tablet where you can scan your RxScale Wallet Pass. After scanning, you
continue on your own phone using a QR code or, if available, an email link.
## What happens on the tablet
The tablet shows the pharmacy branding and asks you to scan your Wallet Pass. It does not show your
name, email address, phone number, date of birth, or prescription details.
## Continuing on your phone
If your Wallet Pass is recognised, choose one of these options:
* **Show QR** — scan the QR code with your phone.
* **Send email** — receive a secure link at the email address already connected to your Wallet Pass.
If the Wallet Pass is not recognised, you can still open a general pharmacy or telemedicine link by
scanning a QR code. Email is not offered in this case.
## Privacy
The tablet resets after use, refresh, or inactivity. It does not save your scan result, contact
details, or login link in browser storage.
Always open the QR code or email link on your own phone. Ask pharmacy staff for help if the tablet
does not recognise your Wallet Pass.
# API Integration
Source: https://docs.rxscale.com/for-pharmacists/api-integration
How to integrate with the RxScale External Pharmacy API for automated order processing
# API Integration
The RxScale External Pharmacy API lets you automate your order processing workflow. Instead of manually managing orders through the Pharmacy Portal, you can connect your existing pharmacy systems directly to RxScale.
## Overview
The External Pharmacy API allows you to:
* **Receive orders automatically** -- Get notified in real time when new orders are assigned to your pharmacy.
* **Update order statuses** -- Programmatically move orders through processing stages.
* **Manage stock levels** -- Keep your inventory in sync between your pharmacy system and RxScale.
* **Track shipments** -- Update shipping information through the API.
This is ideal for pharmacies that want to reduce manual work and integrate RxScale into their existing software and processes.
## Setting Up API Keys
To use the API, you need an API key. You can manage your API keys from the API Access section of the Pharmacy Portal.
Navigate to the API Access section from the sidebar.
Generate a new API key. Give it a descriptive name so you can identify it later (for example, "Pharmacy Management System Integration").
Copy the API key and store it in a safe location. You will not be able to see the full key again after leaving this page.
Treat your API key like a password. Do not share it or expose it in public code repositories. If you believe your key has been compromised, revoke it immediately and create a new one.
## Registering Webhooks
Webhooks let RxScale notify your system automatically when something happens, such as a new order being assigned to your pharmacy or an order being cancelled.
To set up webhooks:
1. Navigate to the API Access section of the Pharmacy Portal.
2. If you manage multiple pharmacy groups, select the group you want to configure from the dropdown at the top of the page.
3. Add a webhook endpoint URL -- this is a URL on your server where RxScale will send notifications.
4. Select the event types you want to receive (for example, new orders, status changes).
5. Save your webhook configuration.
Webhook subscriptions are scoped to a pharmacy group. If you have multiple pharmacy groups, you need to configure webhooks separately for each group.
Once configured, RxScale will send an HTTP POST request to your endpoint whenever a relevant event occurs.
Make sure your webhook endpoint is publicly accessible and responds with a 200 status code to acknowledge receipt. See the [Webhooks documentation](/webhooks/overview) for details on payload formats and security.
## Automating Order Processing
With the API and webhooks in place, you can build an automated workflow:
1. **Receive a webhook notification** when a new order is assigned to your pharmacy.
2. **Fetch the order details** using the API to get the full order information.
3. **Process the order** in your pharmacy system (check stock, prepare medication).
4. **Update the order status** through the API as you move through each processing step.
5. **Add shipping information** once the order is shipped.
This automation eliminates the need to manually check the Pharmacy Portal for new orders and manually update statuses.
## Full API Reference
For complete API documentation, including all available endpoints, request formats, and response schemas, see the [External Pharmacy API Reference](/api-reference/external-pharmacy/overview).
## Related Topics
* [External Pharmacy API Overview](/api-reference/external-pharmacy/overview) -- Full API documentation.
* [Webhooks Overview](/webhooks/overview) -- How webhooks work, including security and payload details.
* [Webhook Events](/webhooks/events) -- All available event types.
* [Pharmacy Integration Guide](/guides/pharmacy-integration) -- Step-by-step integration guide.
# FAQ
Source: https://docs.rxscale.com/for-pharmacists/faq
Frequently asked questions for pharmacists using the RxScale Pharmacy Portal
# Frequently Asked Questions
Find answers to common questions about using the RxScale Pharmacy Portal.
## Orders
When a new order is assigned to your pharmacy, it appears in your dashboard with a "waiting for pharmacy" status. You will also receive a notification if you have notifications configured. Open the order to review the prescription and product details, verify stock availability, and begin processing.
If you cannot fulfill an order (for example, because the product is out of stock), you can cancel it from your side. The system will then attempt to route the order to another available pharmacy. Contact the support team if you need assistance with a specific order.
If an order is cancelled while you are already preparing it, stop processing and return any picked items to stock. The order status will show as "cancelled" in the portal. You do not need to ship cancelled orders.
Prescriptions that reach your pharmacy have been reviewed and signed. Most prescriptions are signed by a doctor using a qualified electronic signature (QES). Some integrations may provide an externally signed prescription PDF instead. The signed prescription is attached to the order and can be viewed in the order details.
No. Orders that contain only over-the-counter (OTC) products -- items that do not require a prescription -- are routed to your pharmacy without a doctor review or a signed prescription. These orders have no prescription file and no doctor data attached, and the patient has already paid for the products at checkout.
## Inventory
Navigate to the Inventory section from the sidebar. Find the product you want to update by searching by name or SKU. Enter the new stock quantity and save. The change takes effect immediately. For more details, see [Managing Stock](/for-pharmacists/managing-stock).
When a product's stock level reaches zero, it is marked as out of stock. New orders for that product will not be routed to your pharmacy until you replenish the stock. If you have low stock alerts configured, you will receive a notification before you run out.
Products are assigned to your pharmacy by the organisation administrator. If you need to add new products to your inventory, contact your administrator or RxScale account manager.
## Shipping
Subscribe to Pharmacy Suite from Settings or the navigation bar, then open the shipping configuration section. Add your **DHL Paket** credentials and save. A complete configuration is active immediately, and you can create shipping labels from order pages. **DHL Express** and **DPD** are listed as Coming soon. For step-by-step instructions, see the [Shipping](/for-pharmacists/shipping) guide.
Yes. Add your own DHL Paket contract in Settings after you subscribe to Pharmacy Suite. DHL Express and DPD are coming soon. See the [Shipping](/for-pharmacists/shipping) guide.
If a delivery fails (for example, the package is returned to your pharmacy), contact the support team for guidance on next steps. They can help arrange a re-delivery or update the order as needed.
## Account and Access
On the login page, click the "Forgot password" link to receive a password reset email. If you do not receive it, check your spam folder or contact your account manager.
Yes, multiple user accounts can be created for your pharmacy. Contact your organisation administrator to set up additional user accounts.
For technical issues, contact your RxScale account manager or reach out to the support team. They can help with portal access, API issues, and any other technical questions.
# Getting Started
Source: https://docs.rxscale.com/for-pharmacists/getting-started
How to log in and navigate the RxScale Pharmacy Portal
# Getting Started
This page walks you through logging into the Pharmacy Portal, understanding the dashboard, and finding your way around.
## Logging In
Navigate to the Pharmacy Portal URL provided by your RxScale account manager.
Enter the email address and password associated with your RxScale account.
If two-factor authentication is enabled, follow the prompts to verify your identity.
After successful authentication, you will be taken to the Pharmacy Portal dashboard.
If you have not received login credentials, contact your RxScale account manager to get set up.
## Dashboard Overview
The dashboard gives you an at-a-glance view of your pharmacy's activity. When you first log in, you will see:
* **Pending orders** -- Orders that are waiting for you to process.
* **In-progress orders** -- Orders that you are currently working on.
* **Recent activity** -- A summary of recently completed or updated orders.
The dashboard is designed to help you quickly identify what needs your attention so you can prioritise your work.
## Navigating the Pharmacy Portal
The Pharmacy Portal is organised around a sidebar with the following sections:
| Section | What It Does |
| -------------------- | ----------------------------------------------------------------------------------- |
| **Dashboard** | Your main overview showing pending orders, in-progress orders, and recent activity. |
| **Prescriptions** | View prescriptions associated with your pharmacy orders. |
| **Listing requests** | Ask a connected shop to list a product you already carry. |
| **Inventory** | Manage your product catalog, stock levels, and pricing. |
| **Analytics** | View reports and insights about your pharmacy's performance. |
| **API Access** | Manage API keys and webhook configurations for automated integrations. |
| **Settings** | Configure your pharmacy profile, shipping options, and notification preferences. |
## Next Steps
Now that you know your way around, learn how to:
* [Process orders](/for-pharmacists/processing-orders) as they come in
* [Manage your stock](/for-pharmacists/managing-stock) and keep inventory up to date
* [Set up shipping](/for-pharmacists/shipping) for your pharmacy
# Group Inventory
Source: https://docs.rxscale.com/for-pharmacists/group-inventory
How pharmacy groups with a Pharmacy Suite subscription manage shared products, warehouses, batches, and stock across all member pharmacies.
# Group Inventory
Pharmacies that belong to a group with a Pharmacy Suite subscription have access to a shared group
inventory view. This replaces the per-pharmacy inventory page with a catalog that covers all member
pharmacies at once, so you can see total group stock and drill into any individual pharmacy or
warehouse.
Group inventory is available only when your pharmacy group has an active Pharmacy Suite subscription.
Pharmacies whose group is not subscribed continue to use the standard per-pharmacy inventory view.
## Group and Pharmacy Selection
When you open the Inventory section, RxScale resolves which group your pharmacy belongs to. If your
user account is linked to more than one pharmacy group, you are prompted to choose which group to
work with before the catalog loads. Your selection is remembered for the current session.
## Product Catalog
The group catalog lists every product that is registered for your pharmacy group. For each product
you can see:
* **Product name** -- The medication or article name.
* **PZN** -- The pharmacy product number, if recorded.
* **Total stock** -- Combined on-hand units across all member pharmacies and their warehouses.
* **Status** -- Whether the product is active within the group.
### Filtering by Pharmacy
Use the pharmacy filter above the catalog to narrow stock figures to a single member pharmacy. When
a pharmacy is selected, the **Total stock** column reflects that pharmacy's on-hand quantity only.
Remove the filter to return to group-wide totals.
## Warehouses
Each member pharmacy can have one or more warehouses (for example, a dispensary and a cold-chain
store). You manage warehouses per pharmacy from the Warehouses section.
### Creating a Warehouse
Navigate to Inventory and select the Warehouses tab.
Click **New warehouse** and enter a name for the warehouse (for example, "Offizin" or
"Cold-chain store").
Confirm the dialog. The warehouse is immediately available for goods receipts.
## Product Detail
Click any product in the catalog to open its detail view. The detail view has three sections:
availability, batches, and movement history.
### Availability
The availability section shows stock figures for the pharmacy currently selected in the pharmacy
selector. When a specific pharmacy is selected, you see the following figures for that pharmacy:
* **On hand** -- Total units physically present across all that pharmacy's warehouses.
* **Reserved** -- Units currently held for open orders and not yet available to sell.
* **Safety buffer** -- The quantity the pharmacy holds back from online availability.
* **Effective sellable** -- On-hand minus reserved minus safety buffer (never negative). This is the
quantity the pharmacy exposes to the online channel.
If no specific pharmacy is selected (the pharmacy selector shows all pharmacies), the section
prompts you to select a pharmacy first.
### Batches
The batches section lists every batch currently in stock for this product, across all member
pharmacies. For each batch you can see:
* **Batch number** -- The identifier printed on the pack.
* **Expiry date** -- When the batch expires.
* **Warehouse** -- Which warehouse holds this batch.
* **On hand** -- Units in this batch.
* **Status** -- Whether the batch is available, blocked, or recalled.
### Movement History
The movement history shows every stock change for this product across the group — goods receipts,
reservations, depletions, and corrections. Movements are read-only and cannot be edited or deleted.
## Receiving Stock (Goods Receipt)
To add new stock for a product, use the **Receive stock** action on the product detail page.
Find the product in the catalog and click it to open the detail view.
Select the **Receive stock** action. A dialog opens.
Choose the target warehouse, enter the batch number, expiry date, and quantity received. You can
also add an optional supplier reference and purchase price.
Submit the dialog. RxScale records the receipt as an inventory movement and the stock is
immediately reflected in the catalog.
If a product is marked as requiring a batch, both a batch number and an expiry date are mandatory.
When the pack shows only a month and year, RxScale stores the expiry as the last day of that month.
## Mapping a SKU
The **Map SKU** action links a group product to a telemedicine provider's SKU so that orders for
that SKU can be fulfilled from this product's stock. Open the product detail page and select **Map
SKU** to create or update the mapping.
## Related Topics
* [Native Inventory](/for-pharmacists/inventory-base-erp) -- Full reference for the Pharmacy Suite
inventory model, including reservations, dispensing, and availability buffers.
* [Managing Stock](/for-pharmacists/managing-stock) -- Standard per-pharmacy stock management for
pharmacies without a group subscription.
# Native Inventory
Source: https://docs.rxscale.com/for-pharmacists/inventory-base-erp
Manage pharmacy products, warehouses, batches, and goods receipts in RxScale.
# Native Inventory
RxScale native inventory separates product identity from physical stock and channel
availability. Products are shared within a pharmacy group, while stock belongs to a warehouse in
a specific pharmacy.
Native inventory is part of the Pharmacy Suite subscription. It is opt-in and does not change how
your shop availability or order fulfilment works until you enable it.
## Products
A pharmacy group product represents the article your pharmacy dispenses. It can store identifiers
such as PZN, product name, dosage form, and unit, plus flags for prescription-only, cannabis,
cold-chain, batch-required, and narcotic products.
## Warehouses
Each pharmacy has at least one warehouse, usually the dispensary. Additional back-stock or
cold-chain warehouses can be added when needed. Physical stock always belongs to a warehouse; a
pharmacy's stock is the sum of its warehouses.
## Goods Receipt
Goods receipt brings stock into a warehouse. RxScale records the target warehouse, product, batch
number, expiry date, quantity, an optional supplier reference, an optional purchase price, and the
user who received the goods.
Products marked as requiring a batch must be received with a batch number and expiry date. When
only a month and year are printed on the pack, RxScale stores the expiry as the last day of that
month, so the same charge always resolves to a single batch.
## Stock History
Every receipt creates an inventory movement. Movements are append-only, so stock changes can be
audited later and never silently overwritten.
## Availability Buffers
Physical stock is not always the same as online availability. For each product, a pharmacy can set
an availability policy that holds back a safety buffer, so the shop only exposes stock above that
buffer. The online sellable quantity is calculated as on-hand stock, minus quantities already
reserved by open orders, minus the safety buffer, and never goes below zero.
For example, if a pharmacy has 20 packs on hand and sets a safety buffer of 5, RxScale exposes at
most 15 packs. You can also set an optional maximum sellable quantity to cap exposure further, or
mark a product as unavailable to hold it back entirely.
Safety buffers are product-specific. A pharmacy can use a higher buffer for shortage-prone products
and no buffer for normal stock. Buffers can also be overridden for a specific sales channel
(organisation or shop) when one channel needs different limits than the default.
## Reservations
When an order line has a confirmed product mapping, RxScale can reserve eligible batches before
handover. A reservation holds stock for that order but does not reduce on-hand stock — actual
depletion happens later, at pick or ship confirmation.
Reservations follow first-expiry-first-out (FEFO): RxScale picks the nearest-expiry eligible batches
first and skips any expired, blocked, or recalled batch. Cold-chain products are reserved only from
cold-chain warehouses. When one batch cannot cover the full quantity, the reservation is split across
batches automatically.
The quantity reserved is derived from the order line — the ordered amount multiplied by the mapping's
quantity per unit — so it always matches what the patient actually ordered. If a line cannot be fully
reserved, it is flagged: `out_of_stock` when there is not enough sellable stock, or
`batch_blocked_or_expired` when stock exists only in batches that are expired, blocked, or recalled.
If a reserved order is cancelled, its reservation is released and the held stock is returned
automatically — cancelling never leaves stock stranded. Re-running a reservation on the same line
first releases the previous hold, so a line is never reserved twice.
## Dispensing
Dispensing converts a held reservation into an actual stock depletion at pick or ship confirmation.
RxScale decreases both the on-hand and the reserved quantity of the affected batches, writes an
append-only depletion movement, and creates an attributable dispensing record. The record stores the
licensed pharmacy responsible for handover, the user who confirmed the dispensing, the timestamp, the
batch and quantity dispensed, and the signature method used (`paper`, `aes`, or `qes`). When a
qualified electronic signature is used, the record also references the underlying signing request.
The quantity dispensed is taken from the line's own reservation, never from the request — RxScale
depletes exactly what the line still holds. Before depleting, RxScale re-checks every affected batch:
if a batch became blocked, recalled, or expired between reservation and dispensing, the dispensing is
refused and the stock stays reserved. This prevents compromised or cold-chain-excursion stock from
ever being handed over.
Dispensing can be split across shipments. Each shipment records its own dispensing record and depletes
only the batches it ships, and the order line is only marked as dispensed once its full quantity has
been handed over. Dispensing records, like all inventory movements, are append-only: a correction is
recorded as a new compensating entry, never an in-place edit.
# Invoices & Delivery Notes
Source: https://docs.rxscale.com/for-pharmacists/invoices-and-delivery-notes
How pharmacies with a Pharmacy Suite subscription configure invoice settings, issue a formal patient invoice (Rechnung) with gapless numbering, and print a delivery note (Lieferschein) from an order.
# Invoices & Delivery Notes
Pharmacies with a Pharmacy Suite subscription can issue a formal patient invoice (Rechnung) and print
a delivery note (Lieferschein) directly from a pharmacy order. Both documents are generated as PDFs by
RxScale and open ready to print. The invoice carries a gapless, sequential invoice number, a per-line
net/VAT/gross breakdown grouped by VAT rate, and a snapshot of your pharmacy and the patient at the
moment it is issued.
Invoices and delivery notes are available only when your pharmacy has an active Pharmacy Suite
subscription. If the actions do not appear on the order detail page, or the invoice settings card is
missing from Settings, your pharmacy is not subscribed — contact your RxScale account manager.
## Before You Start: Configure Invoice Settings
Before you can issue your first invoice, you must complete your invoice settings. The legal header,
tax details, and numbering that appear on every invoice come from this configuration — an invoice
cannot be issued until the required fields are filled in.
Open **Settings** in the Pharmacy Portal and find the **Invoicing** card.
### Legal Header and Contact Details
These fields form the seller block printed at the top of every invoice and delivery note:
* **Legal name** -- The registered name of your pharmacy. *Required.*
* **Address line 1**, **Address line 2**, **Postal code**, **City**, **Country code** -- Your
pharmacy's address. Address line 1, postal code, and city are *required*.
* **Website** -- Your pharmacy's website, if you want it shown.
* **Contact email**, **Contact phone** -- Optional contact details.
### Tax Details
* **Tax number** -- Your pharmacy's tax number (Steuernummer). *Required.*
* **VAT ID** -- Your VAT identification number (USt-IdNr.), if applicable.
### Bank Details
Shown in the payment block of the invoice:
* **IBAN**, **BIC**, **Bank name** -- Your bank account for payment.
### Numbering
* **Invoice number prefix** -- An optional text prefix placed before the number (for example, `RE-`).
* **Starting invoice number** -- The number your first invoice will use. RxScale then increments it
automatically for every following invoice.
The starting invoice number **locks permanently once your first invoice has been issued**. Gapless,
sequential numbering is a legal requirement, so the number cannot be changed or reset afterwards. Set
your desired starting number **before** you issue any invoice. Once the first invoice exists, the
field is disabled and shows "Locked once the first invoice has been issued".
### Default VAT Rate
* **Default VAT rate** -- The fallback VAT rate applied to a line when its product has no rate of its
own. It is entered in basis points, where `1900` means 19% and `700` means 7%.
### Logo
Use **Upload logo** to add your pharmacy logo to the invoice header. Upload a PNG or JPEG image.
Navigate to Settings in the Pharmacy Portal and locate the Invoicing card.
Enter at least your legal name, address line 1, postal code, city, and tax number. Add bank and
contact details as needed.
Choose an invoice number prefix (optional) and your starting invoice number. Double-check the
starting number — it cannot be changed after the first invoice.
Use Upload logo to add a PNG or JPEG image to the invoice header.
Click Save. Your settings are stored and invoicing is ready to use.
## Managing VAT Rates
Below the Invoicing card, the **VAT rates** card manages the named VAT-rate templates for your
pharmacy group (for example, "Standard rate" at 19% and "Reduced rate" at 7%). Products reference
these templates, and any line without a specific rate falls back to the group's default rate.
For each VAT rate you can see its **Name**, its **Rate**, and whether it is the group **Default**.
Click **Add VAT rate**, enter a name (for example, "Standard rate") and the rate as a percentage,
then confirm.
Use the **Set as default** action on a rate to make it the group default. Exactly one rate is the
default at a time, so setting a new default clears the previous one.
Use **Edit** to change a rate's name or percentage. Use **Delete** to remove one.
A VAT rate that is still assigned to one or more products cannot be deleted. Reassign those products
to another rate first, then delete the rate.
### How a Line's VAT Rate Is Chosen
For each invoice line, RxScale resolves the VAT rate in this order:
1. The VAT rate assigned to the product, if it has one.
2. Otherwise, the group's **default** VAT rate.
3. Otherwise, the **Default VAT rate** from your invoice settings.
## Issuing an Invoice
Open the order in the Pharmacy Portal and use the **Create invoice** action on the order detail page.
Find the order and open its detail page.
Select the **Create invoice** action. RxScale generates the invoice PDF and opens it ready to
print, and confirms with "Invoice created".
The invoice includes:
* A **gapless, sequential invoice number** (your prefix plus the next number in the sequence).
* One line per order item with **quantity, net, VAT, and gross** amounts.
* **VAT totals grouped by rate**, plus the net total and the total amount due.
* A snapshot of your **pharmacy** (legal header, tax details, bank details) and the **patient**.
Prices on the invoice come from the amounts captured on the order when it was placed — the same
figures the picklist uses — so the invoice always matches what the patient paid, including any item
substitutions.
### One Invoice per Order, Reprinting, and Immutability
Each order can have **exactly one** invoice. The invoice PDF is generated once and stored, so it never
changes after it is issued.
* **Reprinting:** Selecting **Create invoice** again on an order that already has an invoice does not
create a new invoice or use up a new number. It re-opens the **identical** stored PDF — same number,
same figures, byte-for-byte.
* **Immutable:** Because the invoice stores a snapshot at the moment of issue, later changes to the
order or to your pharmacy settings do **not** alter an already-issued invoice.
## Printing a Delivery Note
A delivery note (Lieferschein) lists the products and quantities in an order **without any prices** —
useful to include in the package.
Open the order and use the **Print delivery note** action on the order detail page. RxScale generates
the delivery-note PDF and opens it ready to print.
Unlike the invoice, a delivery note carries **no prices and no invoice number**, and it does not
consume an invoice number. You can print it as many times as you need.
## Related Topics
* [Processing Orders](/for-pharmacists/processing-orders) -- How to receive, accept, and complete
pharmacy orders.
* [Group Inventory](/for-pharmacists/group-inventory) -- Shared product, warehouse, and stock
management for pharmacy groups on the Pharmacy Suite subscription.
* [Settings](/for-pharmacists/settings) -- Configure your pharmacy account, shipping, and
notification preferences.
# Joining RxScale
Source: https://docs.rxscale.com/for-pharmacists/joining-rxscale
Use the invite link from your telemedicine partner to connect your pharmacy.
# Joining RxScale
Your telemedicine partner can share an invite link that connects your
pharmacy to their RxScale organisation. The link is bearer-credentialed —
treat it like a one-time access code.
## Open the invite link
The admin shares the link through their own channel (email, WhatsApp,
etc.). Click it on a desktop browser.
If the link is no longer valid (expired or already used) you'll see a
**"This link is no longer valid"** screen. Ask the admin for a fresh link.
## Sign in or sign up
The page offers a **Continue with RxScale** button. Click it. The Auth0
dialog handles both signing into an existing RxScale account and creating a
fresh one.
## Pick or create a pharmacy group
After login, you'll see your pharmacy groups (if any). You can:
* Pick an existing group to connect it to the telemedicine partner. If the
group already serves them, the form shows an **"Already connected"**
badge — submitting it is a no-op success.
* Create a brand-new group by picking the **"Create a new pharmacy group"**
option and giving it a name.
Optionally set a pharmacy display name; the field defaults to the group
name when left empty.
## What success looks like
You'll see either **"X can now route prescriptions to your pharmacy"**
(new connection) or **"You're already set up to receive prescriptions from
X"** (already connected). Either way, you're done.
## If you see "Finishing your account setup"
Sometimes the page lands a half-second before your account fully
provisions in our backend. Wait a moment and click **Try again** — it'll
clear on its own.
# Listing Requests
Source: https://docs.rxscale.com/for-pharmacists/listing-requests
Ask a shop to list a product your pharmacy already carries
# Listing Requests
If you stock an item that a connected shop does not yet list, you can file a
**listing request** from the Pharmacy Portal. The shop reviews the request in
its admin inbox and accepts or declines it.
Accepting a request records the decision only. It does not automatically create
a product listing in the shop.
## Open listing requests
In the sidebar, open **Listing requests**. The list is scoped to the pharmacies
you belong to. Use the pharmacy and shop selectors in the header, plus the
status filter on the page, to narrow the table.
## Create a request
1. Choose **New request**.
2. Select the shop (and pharmacy, if you serve more than one).
3. Fill in the product name and any fields the shop has configured. Typical
shops ask for a PZN; that value is an extra field, not a separate top-level
box.
4. Optional notes help the shop decide. Price indication, when shown, is
entered in cents (minor units), for example `1250` for €12.50.
The form is generated from the shop's current requirements. Required fields are
marked. Empty optional extras are omitted and are not stored as blank values.
A shop that has already received a pending request with the same PZN will
reject a duplicate.
## After you submit
The request starts as **PENDING**. Open it from the table to review the
submitted values. While it is pending you can **withdraw** it. After the shop
accepts, declines, or you withdraw, the status no longer changes from the
pharmacy side.
# Managing Stock
Source: https://docs.rxscale.com/for-pharmacists/managing-stock
How to manage your inventory, stock levels, and pricing in the Pharmacy Portal
# Managing Stock
Keeping your inventory accurate is essential for smooth order processing. This page explains how to view and update your product catalog, stock levels, and pricing in the Pharmacy Portal.
## Viewing Your SKU List
The Inventory section of the Pharmacy Portal shows all products (SKUs) that are assigned to your pharmacy. For each product, you can see:
* **Product name** -- The name of the medication or product.
* **SKU** -- The unique identifier for the product variant.
* **Current stock level** -- How many units you currently have available.
* **Price** -- The current price for this product.
* **Status** -- Whether the product is active, out of stock, or disabled.
## Updating Stock Levels
To keep your inventory accurate, update your stock levels whenever your physical inventory changes. This helps prevent orders from being routed to your pharmacy for products you do not have in stock.
Open the Inventory section from the sidebar.
Search for the product by name or SKU.
Enter the new stock quantity. The change takes effect immediately.
If you receive a shipment of new stock, update your levels right away. This ensures that orders can be routed to your pharmacy as soon as the products are available.
## Activating and Deactivating Products
You can activate or deactivate inventory at two levels:
* **Product level** -- Applies the change to all variants of the product in your pharmacy scope.
* **SKU level** -- Applies the change only to one specific product variant.
Deactivating a product or SKU sets its stock level to `0`. Orders will not be routed to your pharmacy for that item until it is activated again with a positive stock amount.
When activating a product or SKU, enter the stock amount that should be available. For product-level activation, the entered stock amount is applied to all variants included in that product group.
## Updating Prices
You can update the price of any product in your inventory. Price changes take effect immediately and will apply to new orders. Existing orders that have already been placed will not be affected by price changes.
To update a price:
1. Navigate to the Inventory section.
2. Find the product you want to update.
3. Enter the new price.
4. Save your changes.
## Stock Thresholds and Notifications
RxScale can notify you when your stock levels fall below a certain threshold. This helps you reorder products before you run out.
* **Low stock threshold** -- When your stock level drops below this number, you will receive a notification.
* **Out of stock** -- When your stock level reaches zero, the product is marked as out of stock and orders for this product will not be routed to your pharmacy until stock is replenished.
Configure your stock thresholds in the Settings section of the Pharmacy Portal.
## Tips for Inventory Management
* **Update stock regularly.** The more accurate your stock levels, the fewer order issues you will encounter.
* **Set appropriate thresholds.** Choose low-stock thresholds that give you enough time to reorder before running out.
* **Review your product list periodically.** Make sure all products assigned to your pharmacy are ones you actually carry. Contact the support team if you see products that should not be listed.
## Related Topics
* [Processing Orders](/for-pharmacists/processing-orders) -- How to handle incoming orders.
* [Settings](/for-pharmacists/settings) -- Configure stock threshold notifications and other preferences.
# Pharmacy Portal
Source: https://docs.rxscale.com/for-pharmacists/overview
Your guide to using the RxScale Pharmacy Portal
# Welcome to the RxScale Pharmacy Portal
This guide helps you get the most out of the RxScale Pharmacy Portal. Whether you are processing orders, managing your inventory, or setting up shipping, you will find clear instructions here for every step.
## What Can You Do in the Pharmacy Portal?
The Pharmacy Portal is your central workspace for managing prescription orders and inventory within the RxScale platform. Here is what you can do:
View incoming orders, accept them, and track their progress through to completion.
Keep your inventory up to date by managing SKUs, stock levels, and pricing.
Configure shipping carriers, create labels, and track deliveries.
Automate your workflow by integrating with the RxScale External Pharmacy API.
Ask a connected shop to list a product you already carry.
## Getting Started
If this is your first time using the Pharmacy Portal, start with the [Getting Started](/for-pharmacists/getting-started) guide to learn how to log in and navigate the dashboard.
## Help Center
For detailed explanations of statuses, workflows, and platform concepts, visit the [Help Center](/help-center/overview):
* [Pharmacy Order Statuses](/help-center/pharmacy-order-statuses) — What each pharmacy order status means
* [Fulfillment Statuses](/help-center/fulfillment-statuses) — Fulfillment workflow stages
* [Order Statuses](/help-center/order-statuses) — End-to-end order status reference
* [Delivery Types](/help-center/delivery-types) — Available delivery options
* [Notifications](/help-center/notifications) — How webhook notifications work
## Need Help?
If you need to contact the support team for a specific shop, use the built-in [Support Contact](/for-pharmacists/support) feature directly from the Pharmacy Portal. You can also check the [FAQ](/for-pharmacists/faq) for answers to common questions, or contact your RxScale account manager.
# Processing Orders
Source: https://docs.rxscale.com/for-pharmacists/processing-orders
How to view, accept, process, and complete orders in the Pharmacy Portal
# Processing Orders
Orders are routed to a pharmacy for fulfillment once they are ready to be prepared. For most orders this happens when a doctor signs a prescription. Orders that contain only over-the-counter (OTC) products -- items that do not require a prescription -- are routed to a pharmacy the same way, without a doctor review step. This page explains how to handle orders from the moment they arrive in your queue through to completion.
## Viewing Incoming Orders
New orders appear in your dashboard and in the Prescriptions section. Each order includes:
* **Patient information** -- The patient's name and delivery address.
* **Prescribed medication** -- The products and quantities requested.
* **Prescription details** -- The signed prescription associated with the order.
* **Delivery type** -- Whether the order is for shipping or pharmacy pickup.
If your pharmacy has the shop order name enabled, the shop order number (e.g. `#1234`) is also shown in the **Order info** column of the prescriptions table. If the setting is disabled, no shop order number is shown -- your shop can enable it itself in the admin tool (on the pharmacy's detail page, under the **Shop Order Name** setting); no RxScale involvement is required.
Some orders contain only over-the-counter (OTC) products and have no prescription attached -- no doctor was involved, and there is no prescription file to review or download. These orders still include patient information, requested products and quantities, and delivery type.
## Order Statuses
Orders move through a series of statuses as you process them. Here is what each status means from your perspective:
| Status | What It Means | What To Do |
| ------------------------ | ------------------------------------ | ------------------------------------------------------- |
| **Waiting for pharmacy** | The order has arrived in your queue. | Review the order and check stock availability. |
| **Pending review** | You are reviewing the order. | Verify the prescription and product availability. |
| **In-progress** | You are preparing the order. | Pick and pack the medication. |
| **Ready for pickup** | The order is packed and ready. | Ship the order or prepare it for patient pickup. |
| **Completed** | The order has been fulfilled. | No further action needed. |
| **Cancelled** | The order was cancelled. | Stop processing and return items to stock if necessary. |
## Processing an Order
Open the order to see the prescribed medication, patient details, and prescription. Verify that you have the required products in stock.
Once you have confirmed stock availability, accept the order. This moves the order to "in-progress" and lets the system know you are working on it.
Pick the products from your inventory, verify them against the prescription, and pack them for shipping or pickup.
Once the order is packed and ready, update the status. If shipping, select the carrier, add the tracking link in the completion dialog, review the shipment details in the order, and hand the package to your carrier. If pickup, have the order ready at the counter.
## When a Status or Delivery Type Change Is Refused
The prescriptions list shows the order as it was when the page last loaded. If the order has moved on since then -- a colleague advanced it, or it was cancelled upstream -- the control you click may no longer apply, and the change is refused.
When that happens you get a message explaining which rule refused it, and the list reloads so you can see the order's current state:
| Message | What it means | What to do |
| ----------------------------------------------------------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| This order was cancelled, so its status can no longer be changed. | The order was cancelled while your list was open. | Stop processing it and return any picked items to stock. |
| The delivery type can no longer be changed at this order status. | Delivery type is editable only up to and including "in-progress". | If the delivery type is genuinely wrong at this stage, contact the support team. |
| Set a delivery type before marking this order ready for pickup. | The order has no delivery type yet. | Set the delivery type to pickup first, then mark it ready. |
| Only pickup orders can be marked ready for pickup. | The order is a shipping order. | Complete it with a carrier and tracking link instead. |
| This status change is not allowed for this order. | The order cannot move to that status from where it is now. | Check the current status in the reloaded list and pick a valid next step. |
Refusals are normal -- they are the system protecting an order that has already moved on, not an error on your side. If the same change is refused repeatedly on an order that looks correct after a reload, contact the support team.
## Handling Cancelled Orders
Orders can be cancelled at various stages. If an order is cancelled:
* **Before you start processing** -- Simply acknowledge the cancellation. No further action is needed.
* **While you are preparing it** -- Stop processing, return any picked items to stock, and update the order status.
* **After shipping** -- Contact the support team for guidance on handling returns.
Always check the order status before shipping. An order may have been cancelled between the time you started preparing it and the time you are ready to ship.
## Payment Status
Each order displays a payment status badge in the prescriptions table and in the order detail view:
* **Prepaid** -- The patient paid during checkout before the order reached the pharmacy.
* **Payment pending** -- No payment has been recorded for the pharmacy order yet.
* **Partially paid** -- One or more manual payments have been recorded, but the total does not yet cover the order amount.
* **Payment confirmed** -- Recorded payments cover the order amount.
For orders that are paid at the pharmacy, record each manual payment in the order's payment section. The payment ledger shows manual payments and routed payment entries, including references, notes, and voided entries. If a manual payment was entered incorrectly, void the payment with a reason and add a corrected payment instead of editing the original row.
Over-the-counter (OTC) orders -- orders with no prescription attached -- were paid for by the patient during checkout, so no separate payment collection is needed. Because the payment badge is driven by prescription type, an OTC-only order currently displays as **Unpaid** (yellow); treat that badge as informational only for OTC orders and do not withhold or request payment based on it.
## Downloading Prescriptions
You can download prescription PDFs directly from the prescriptions table.
### Single Download
Click the download icon next to any order to download that order's prescription as a PDF file.
### Bulk Download
To download multiple prescriptions at once:
Use the checkboxes in the prescriptions table to select the orders you want to download. You can use the checkbox in the header row to select all orders on the current page.
Click the "Download selected" button that appears in the toolbar. This downloads all selected prescriptions as a single ZIP file containing individual PDFs.
You can select up to 100 orders per bulk download. The ZIP file contains one PDF per order, named with the order's unique identifier.
### Including the Shop Order Number in the Filename
By default, downloaded prescription files are named with the order's identifier. If your pharmacy has the shop order name enabled, you can optionally include the shop order number in the downloaded filename. Turn this on under **Settings** — once enabled, single and bulk prescription downloads are named like `prescription--.pdf`. This only changes the filename; the prescription PDF itself is never modified.
This option appears in Settings only when the shop order name is enabled for your pharmacy. If you do not see it, your shop can enable the shop order name itself in the admin tool first (on the pharmacy's detail page, under the **Shop Order Name** setting).
## Tips for Efficient Order Processing
* **Process orders promptly.** Quick turnaround improves patient satisfaction and keeps the workflow moving.
* **Keep statuses up to date.** Accurate status tracking helps patients and the RxScale team know exactly where each order stands.
* **Check stock before accepting.** Verify that you have the required products before accepting an order to avoid delays.
* **Set up notifications.** Configure your notification preferences in Settings so you are alerted when new orders arrive.
## Related Topics
* [Pharmacy Order Statuses](/help-center/pharmacy-order-statuses) -- Detailed explanation of all pharmacy order statuses.
* [Order Statuses](/help-center/order-statuses) -- How order statuses work across the entire system.
* [Shipping](/for-pharmacists/shipping) -- How to set up and manage shipping.
# Settings
Source: https://docs.rxscale.com/for-pharmacists/settings
How to configure your pharmacy account, shipping, and notification preferences
# Settings
The Settings section of the Pharmacy Portal lets you configure your pharmacy profile, shipping options, and notification preferences. This page covers the key settings you can manage.
## Account Settings
Your account settings include your pharmacy's basic information:
* **Pharmacy name** -- The name displayed in the system.
* **Contact information** -- Email address and phone number for your pharmacy.
* **Address** -- Your pharmacy's physical address.
Keep this information up to date so that patients and the RxScale team can reach you when needed.
## Shipping Configuration
Shipping settings control how you create and manage shipping labels. Here you can:
* **Configure your shipping carrier** -- Add or update your carrier account details.
* **Set default shipping options** -- Choose your preferred shipping method and packaging settings.
* **Manage return label settings** -- Configure how return labels are generated for your pharmacy.
For detailed instructions on setting up shipping, see the [Shipping](/for-pharmacists/shipping) guide.
## Notification Preferences
Control what notifications you receive and how you receive them:
* **New order notifications** -- Get alerted when a new order is assigned to your pharmacy.
* **Low stock alerts** -- Receive a notification when a product's stock level falls below your configured threshold.
* **Order update notifications** -- Stay informed about changes to orders, such as cancellations.
Configuring your notifications helps you stay on top of your workflow without having to check the portal constantly.
## Linking Pharmacies to Shops
If your pharmacy serves multiple shops (online stores), you can manage these connections in the Settings section. Linking a pharmacy to a shop means that orders from that shop can be routed to your pharmacy for fulfillment.
* **View linked shops** -- See which shops are currently connected to your pharmacy.
* **Manage links** -- Your organisation administrator or RxScale account manager can add or remove shop links based on your needs.
Shop linking is typically managed by your organisation administrator. If you need to add or remove a shop connection, contact your administrator or account manager.
## Tips
* **Review your settings periodically.** Make sure your contact information, shipping configuration, and notification preferences are current.
* **Set up notifications early.** Configuring notifications when you first start using the portal ensures you never miss an important order or stock alert.
* **Keep shipping details accurate.** Incorrect carrier information can cause delays in label creation and shipping.
## Related Topics
* [Getting Started](/for-pharmacists/getting-started) -- Overview of the Pharmacy Portal.
* [Shipping](/for-pharmacists/shipping) -- Detailed shipping setup instructions.
* [Managing Stock](/for-pharmacists/managing-stock) -- Inventory management and stock thresholds.
# Shipping
Source: https://docs.rxscale.com/for-pharmacists/shipping
How to set up shipping and track deliveries
# Shipping
This page explains how to configure your shipping setup and track deliveries through the Pharmacy Portal.
## Pharmacy Suite subscription
Creating shipping labels in the Pharmacy Portal requires an active **Pharmacy Suite** subscription. Subscribe from Settings or from the status control in the navigation bar. After checkout, the portal confirms the subscription and unlocks carrier setup.
Without an active subscription, label and return-label actions stay disabled. You can still view shipment details and add tracking information when completing a shipped order.
If Subscribe does not appear, or checkout does not complete, contact your RxScale account manager.
## Setting Up Shipping
Once you are subscribed, configure your own carrier contract in Settings. RxScale books labels through that contract — there is no extra provisioning step, and a complete configuration is ready to use immediately.
Navigate to the Settings section from the sidebar.
Find the shipping configuration area. This section is available after your Pharmacy Suite subscription is active.
Choose a carrier and enter the contract details. Currently available:
* **DHL Paket (Germany)** — your Geschäftskundenportal system user and billing number.
**DHL Express** and **DPD** appear in the carrier list as **Coming soon** and cannot be selected yet.
Save the form. When the sender address, service, and required credentials are filled in, the configuration is active and you can print labels from an order. Incomplete configurations stay pending until those fields are complete.
Return labels are available for **DHL Paket**. Other carriers will follow when they become available.
## Test Mode
You can switch **DHL Paket (Direct)** into test mode. This is a setting on the carrier configuration — not a choice you make per order.
While test mode is on for a carrier, RxScale books that carrier's labels against DHL's sandbox using RxScale's own test credentials instead of your DHL contract. No parcel is dispatched, the tracking number is not real, and the shipment is not billed.
Test mode is available for **DHL Paket (Direct)** only. DPD and DHL Express do not have this setting.
The **Test mode** checkbox is in the carrier form in Settings, both when you add the carrier and when you update its credentials.
You can always tell when test mode is involved:
* A carrier's card in Settings shows the **Test mode** badge (blue), next to **Default** (green) and **Pending** (yellow), when that carrier's configuration is in test mode.
* A shipment shows the **Test shipment** badge (blue), in the order view and in the full shipments dialog, when it was booked while test mode was on.
Each shipment keeps whichever mode was active when it was booked. Its label and return label continue to go through the same DHL sandbox or live DHL account, even if you change the carrier's test mode setting afterward.
Test mode does not warn you before you use it on a real order. If test mode is on when you create a shipping label, that order gets a sandbox label and is never actually shipped — there is no separate confirmation step. Before shipping a real order, check the carrier card for the **Test mode** badge, and only enable test mode for your own testing.
## Creating Shipping Labels
Open the order and create a shipping label. RxScale sends the order to your configured carrier and returns a printable label.
Once the order is shipped, keep the order status and tracking information up to date.
## Tracking Shipments
After shipping an order, you can track its delivery status from the Pharmacy Portal. Tracking information is typically provided by your shipping carrier and is displayed alongside the order details.
Both you and the patient can see the tracking status, so there is no need to manually notify patients about their delivery progress.
## Return Labels
Return-label creation also requires an active Pharmacy Suite subscription.
Return labels are available for **DHL Paket**.
Return policies and processes may vary depending on your organisation's configuration. Contact your account manager for details about your specific setup.
## Tips for Shipping
* **Double-check addresses.** Before creating a shipping label, verify the delivery address is correct to avoid failed deliveries.
* **Ship promptly.** Once an order is ready, ship it as soon as possible. Patients appreciate quick delivery.
* **Keep tracking information updated.** If there are issues with a delivery, the tracking information helps the support team assist the patient.
## Related Topics
* [Processing Orders](/for-pharmacists/processing-orders) -- How to handle orders from start to finish.
* [Settings](/for-pharmacists/settings) -- Configure your shipping and other preferences.
# Contacting Support
Source: https://docs.rxscale.com/for-pharmacists/support
How to contact shop support from the Pharmacy Portal
# Contacting Support
The Pharmacy Portal provides built-in support contact options so you can quickly reach the support team for any shop linked to your account. You can contact support either for general inquiries or regarding a specific order.
## General Support Contact
To contact support for a shop:
On the Prescriptions page, click the **Contact Shop Support** button in the header area. This button is available whenever at least one of your linked shops has a support email configured.
If your account is linked to multiple shops, a dropdown appears so you can select which shop's support team you want to contact. If you are linked to only one shop, it is selected automatically.
The dialog shows the support email address and offers two options:
* **Copy email address** -- Copies the support email to your clipboard so you can paste it into your preferred email client.
* **Open mail program** -- Opens your default email application with the support address, a pre-filled subject line, and a message template ready for you to complete.
## Order-Specific Support
When you need help with a particular order, you can contact support directly from the order row:
Locate the order in the Prescriptions table.
Click the actions menu on the order row and select **Contact Support**. The dialog opens with the order details (order name and ID) already included.
If the order's pharmacy is linked to multiple shops, choose the relevant shop. Otherwise, the shop is pre-selected automatically.
As with general support, you can either copy the email address or open your mail program. When using the mail program, the subject and message body are pre-filled with the order name and ID for reference.
## When Is Support Available?
The support contact options are visible whenever at least one shop linked to your account has a support email address configured. If you do not see the support button, it means no support email has been set up for your shops yet. Contact your organisation administrator or RxScale account manager to configure support email addresses.
The support email address is configured per shop. If your pharmacy works with multiple shops, each shop may have a different support contact.
## Related Topics
* [Processing Orders](/for-pharmacists/processing-orders) -- How to view, accept, and complete orders.
* [Settings](/for-pharmacists/settings) -- Configure your pharmacy account and preferences.
* [FAQ](/for-pharmacists/faq) -- Answers to common questions.
# Tablet Wallet Pass
Source: https://docs.rxscale.com/for-pharmacists/tablet-wallet-pass
Let patients continue from an in-pharmacy tablet using their Wallet Pass
# Tablet Wallet Pass
Tablet Wallet Pass lets a patient scan their Wallet Pass on a pharmacy tablet and continue on
their own phone. The tablet never shows patient names, email addresses, phone numbers, or other
contact details.
## Before you activate it
You need:
* the Tablet Wallet Pass add-on enabled for the pharmacy;
* at least one active patient referral link;
* branding for the tablet screen, such as display name, optional logo, primary color, and greeting.
The activation switch stays unavailable until the pharmacy has the add-on and at least one active
referral link.
## Tablet URL
After setup, open the tablet URL from the Pharmacy Portal settings page on the in-pharmacy tablet.
Keep the page open in full-screen or kiosk mode. Refreshing the page always starts a new session.
## Patient flow
When the patient scans a known Wallet Pass, the tablet offers two continuation options:
* **Show QR** — the patient scans a QR code with their own phone.
* **Send email** — RxScale sends a secure login link to the email address already linked to the
Wallet Pass.
When the pass is unknown, the tablet only offers generic shop QR links. Email continuation is not
available for unknown passes.
## Privacy
Tablet screens and tablet API responses are intentionally minimal:
* no patient names;
* no email addresses, masked emails, or phone hints;
* no patient IDs or Wallet Pass IDs;
* no patient data stored in browser storage.
Do not use screenshots or browser developer tools on the tablet to identify patients. The tablet
flow is designed only to start a continuation on the patient's own device.
# Managing appointments
Source: https://docs.rxscale.com/for-telemedicine-providers/admin-appointments
Review and cancel patient appointments, and manage scheduling configuration from the admin portal
# Managing appointments
Admins can review scheduled patient appointments and manage the scheduling configuration from the
admin portal. The appointment overview is designed for support and operations teams that need to
check upcoming bookings or cancel a booking on behalf of a patient or provider.
## Where to find appointments
Use the global appointment overview to see bookings across your organisation. You can also open a
patient profile and review that patient's appointments from the patient detail page.
By default, the overview shows future active appointments.
## Filters
You can filter appointments by:
* Doctor
* Appointment type
* Patient
* Status
* Date range
## Appointment statuses
* `held` means the slot is temporarily reserved but not confirmed yet.
* `confirmed` means the appointment is booked.
* `cancelled` means the appointment was cancelled.
* `expired` means a temporary hold expired before confirmation.
* `completed` means the appointment has finished.
* `no_show` means the patient did not attend.
## Cancelling appointments
Admins can cancel active `held` and `confirmed` appointments. A cancellation reason is required.
The reason is stored with the appointment so support and operations teams can understand why the
booking was cancelled.
Admin cancellations are operational actions and are not limited by the patient-facing minimum notice
period. Patient and partner cancellation rules remain separate.
## Booking token secrets
If your organisation mints **patient booking tokens** from a partner backend (for example a Shopify
storefront), each token is signed against a secret that RxScale issues to you. Manage these from
your organisation settings:
* **Create a secret.** RxScale returns a `key_id` (used in the JWT `kid` header) and the secret
value. The value is stored encrypted at rest. Admins can reveal it again any time from the
Settings → Booking secrets page, so re-pasting it into a new minter doesn't require
re-provisioning.
* **Revoke a secret.** Revocation is immediate. Any tokens signed by the revoked key are rejected
on the next call.
### Rotating a secret
1. Create a new secret. You now have two active secrets.
2. Update your minter to sign new tokens with the new `key_id`.
3. Wait for tokens signed with the old key to expire (typically a few minutes given the short
token TTL).
4. Revoke the old secret.
Because each token is routed by its `kid` header, you can rotate without downtime.
## Public booking links
A public booking link is a stable, shareable URL — unlike a booking token secret (above), which a
partner backend uses to mint a one-time link for a specific patient, a public link is meant to be
published somewhere a patient will find it: your website, a doctor's bio, or an email signature.
Anyone with the URL can book. Manage these from **Settings → Public booking links**.
### Creating a link
Each link is scoped to:
* **A shop** — required. Determines which of your shops the link books into.
* **An appointment type** — required. Every link books exactly one kind of appointment.
* **A doctor** — optional. Pin the link to one doctor, for example on their personal bio page, or
leave it open so it books with any available doctor in the shop.
You also choose a name for the URL (lowercase letters, digits and hyphens); RxScale appends a
short random suffix so the full URL can't be guessed. Optional settings include a return URL to
send the patient to after they book, booking instructions shown before they pick a time, and
whether a reason for the visit is required.
A newly created link only starts working once the public booking page itself has been switched
on for your organisation. Reach out to support if you're setting links up ahead of that and
aren't sure whether it's live yet.
### What visitors are asked for
A first-time visitor books with a verified email address, and is then asked for the patient
profile fields you have marked **Required for prescription** for that link's shop (manage these
under **Settings → Profile Fields**). Nothing is hard-coded: mark an insurance number required and
the booking form asks for it; leave date of birth unmarked and it is not asked for.
Two things are deliberately left out:
* **Photo and ID-document fields** are never asked for here — the booking form is a text form with
no upload — so marking one required does not stop patients booking. Collect these later in the
consultation as usual.
* **Fields the patient already has on file** are skipped. A returning patient is only asked for
what is still blank, and a value already on the record is never overwritten by the booking form.
If a shop has no required-for-prescription fields the form can collect, the visitor books on their
verified email address alone.
Each field is labelled with the **name you gave it** on the profile field itself, so what you type
there is what the patient reads — worth a glance if any of your field names are internal shorthand.
A few common fields (first name, last name, date of birth) keep RxScale's own translated labels
instead, so they read correctly in the patient's language. Date fields get a date picker and phone
fields a telephone keypad, based on the field's type rather than its name.
### The URL stays stable
Once created, a link's URL doesn't change by itself. Publish it anywhere and it keeps working
until you deactivate, rotate, or delete it.
### Deactivating, rotating, and deleting
* **Deactivating** a link (switching its Active setting off, from the edit dialog) stops it
accepting new bookings immediately, but keeps the URL and its configuration in place. Nothing
happens to appointments already booked through it. Switch it back on at any time to resume
taking bookings at the same URL.
* **Rotating** keeps the link's name but issues a new random suffix. The previous URL stops
working immediately, everywhere it was published — your website, bios, and email signatures all
need updating to the new one. Use this if a link's URL ended up somewhere you didn't intend, or
you just want a fresh one.
* **Deleting** a link removes it permanently. Like deactivating, it doesn't affect appointments
already booked through it — only future bookings are.
## Provisioning appointment types and meeting rooms
**Appointment types** are configured by the RxScale team during onboarding and on request. They
define duration, hold TTL, cancellation notice, and rebooking rules. Reach out to support if you
need a new appointment type, or a change to an existing one.
**Meeting rooms you manage yourself.** Every doctor gets a persistent video room automatically
when the doctor is created (`persistent_per_provider` strategy), so the patient and the doctor
always land in the same Jitsi room. Open a doctor's detail page to see the room and to create,
rotate, or remove it. The room name is generated for you and is never something you supply.
* **Creating** a room applies to a doctor who has none — for example a doctor added before rooms
were provisioned automatically.
* **Rotating** issues a new room name. Appointments already booked keep the name they were booked
with, because each appointment stores its room name at the moment the slot is held; only
bookings made after the rotation use the new name.
* **Removing** a room stops all new bookings for that doctor. Not even a hold can be created, so
the doctor cannot be booked at all until a room exists again. Appointments already booked are
unaffected and keep working. Create a room again to make the doctor bookable.
Treat a room name as confidential — it is the way into a live consultation.
## Doctor availability windows
**Doctors manage their own bookable windows.** Each doctor sees an Availability page in their
portal where they can add, edit, or remove weekly recurring slots that drive the patient booking
page. As an admin you can review a doctor's current windows from the doctor detail page — useful
when troubleshooting why patients don't see slots, or when auditing coverage — and you can add,
edit or remove both weekly windows and single-date overrides there yourself. Changes you make take
effect for patient bookings immediately, so coordinate with the doctor before altering their
schedule.
## Rebooking on behalf of a patient
You can move an existing appointment to a new slot from the appointment detail page. Rebooking
honours the appointment type's `rebooking_mode` (same doctor vs. any doctor) and minimum notice
window. Admin rebooks are recorded as a new appointment linked to the original via
`previous_meeting_uid`, so the audit trail is preserved.
## Appointment reminders
You configure reminders **per appointment type**. Each reminder rule has:
* a recipient group — `patient`, `doctor`, or `admin`,
* a `minutes_before` offset (e.g. 60 = one hour before the appointment),
* `send_email` and `send_sms` toggles — when both are off, the event still publishes
(partners can subscribe) but RxScale won't dispatch anything itself.
You can stack multiple reminders per group, for example a doctor reminder 24h before plus a
second 15 minutes before. The maintenance manager runs every minute and publishes one
`APPOINTMENT_REMINDER_DUE` event per resolved recipient. Idempotency is guaranteed by an internal
`appointment_reminders_sent` row so the same reminder never fires twice for the same appointment.
To receive these events on your own systems, subscribe `APPOINTMENT_REMINDER_DUE` from the
notification subscriptions page — the existing webhook subscription mechanism handles signing,
custom headers, and retries the same way it does for prescription events.
## Doctor phone numbers for SMS reminders
If your reminder rules turn on `send_sms` for the doctor recipient, RxScale needs a mobile number
on the doctor's profile to deliver the SMS. Doctors can set their own number from the Settings
page in the Doctor Portal; admins can also edit it on the doctor detail page (the "Mobile phone
number" field, separate from the prescription phone field).
## Cancellation, rescheduling, and confirmation emails
RxScale can email your patients, your doctors, and your admins when a scheduled appointment is
**cancelled**, **rescheduled**, or **confirmed**. This is separate from appointment reminders, and
it is **off by default**: nothing is emailed until you create a subscription for it.
### Turning it on
1. Open **Settings → Notifications**. This tab manages both webhook subscriptions and email
notifications.
2. Create a subscription and set **Channel** to **Email**. The event type is
**Patient-doctor meeting updated**.
3. Choose the **Recipient** — **Patient**, **Doctor**, or **Admins**. Each is its own
subscription, so you can email any combination of the three.
4. Tick which changes send an email: **Cancellation**, **Rescheduling**, **Confirmation**, or any
combination. At least one is required.
A reschedule counts only as a rescheduling. If you tick **Cancellation** alone, moving an
appointment emails nobody — the cancellation half of a reschedule is deliberately suppressed,
so there is nothing left for that subscription to match. **Confirmation is independent of that
suppression** — it is not part of a reschedule at all, so ticking Confirmation always emails on a
fresh booking, whether or not Cancellation or Rescheduling are also ticked.
Editing a subscription later lets you change which changes send mail. Recipient and event type are
fixed once created — to switch recipient, add a subscription for the other one. Delete the
subscription to stop the emails entirely.
### What the email says
The email is bilingual (German first, then English) and names the appointment type, the other
party or parties, and the appointment time:
* **Cancellation** — states that the appointment was cancelled, and includes the cancellation
reason when one was given.
* **Rescheduling** — states that the appointment was moved and gives the new time. It never
repeats the old time, and if no new time is available it says only that it was moved.
* **Confirmation** — states that the appointment was confirmed, and gives the appointment time. It
fires once, when a fresh booking is confirmed — a later reschedule of that appointment sends its
own rescheduling email, not a second confirmation.
Patients and doctors are emailed at the contact address RxScale already holds for them; there is
no new field to fill in. A patient or doctor with no contact address on file is not emailed.
**Rescheduling sends one email, not two.** Moving an appointment internally cancels the old
booking and creates a new one, but only the rescheduling email goes out — the patient is never
told "cancelled" and then "moved". Your webhook stream still shows both transitions; see
[Webhook Events](/webhooks/events#meeting-updated-emails).
The wording is fixed and cannot be customised per organisation today, and only these three changes
send email — the other transitions (held, expired, completed, no-show) never do, though they
still fire webhooks. Delivery is best effort per recipient: if several recipients are subscribed
and one send fails, the others are unaffected.
### Admins fan out to every organisation user
Subscribing **Admins** does not pick a single person — it emails **every user in your
organisation** who has an email address on file, the same way appointment reminder notices to
admins already work. There is no per-user opt-out for this recipient; to stop the emails, delete
or edit the subscription.
### Doctors can switch off their own copy
If you subscribe **Doctor**, each doctor can still silence their own copy from the Settings page
in the Doctor Portal, under **Appointment update emails**. Doctors receive these emails —
including confirmations — unless they turn them off. Turning it off only stops that doctor's own
email — **emails to patients and admins are unaffected**.
### Your webhooks do not change
Email is a separate delivery channel on the same subscription mechanism. The
`patient_doctor_meeting_updated` webhook keeps firing on every meeting transition, with an
identical payload, whether or not you have any email subscription. Existing integrations need no
changes.
## Connecting your Mollie account
Paid booking links need your own Mollie account. Connect it once under **Settings -> Payments**
with **Connect with Mollie**; RxScale then creates payments on your account, the money lands in
your balance, and RxScale takes its platform fee from each payment.
**You do not have to wait for Mollie to finish verifying you.** You can connect a Mollie account
that has just been created — during your onboarding call, for example. The connection is stored
and the page shows it as awaiting verification. While it is in that state you cannot sell paid
appointments yet: the billing step of the booking-link dialog stays closed, and no payment can be
created.
RxScale re-checks the account in the background, and again whenever you open the payments page.
As soon as Mollie has verified your account and enabled payments on it, the connection switches to
ready on its own — nobody has to open anything, and there is no need to run the connect flow a
second time. The switch is silent: you will not get an email about it, so check the payments page
if you want to know where your account stands.
If your verification has just come through and you do not want to wait out that hour, use
**Check Mollie status now** on the payments page. It asks Mollie immediately and tells you what
came back — that payments are now enabled, that Mollie has still not enabled them, or that the
access you granted RxScale no longer works and the account has to be reconnected.
If your Mollie account has **more than one verified profile**, RxScale cannot tell which brand a
payment should be created against and leaves the connection unusable. Sort the profiles out in
your Mollie dashboard so exactly one is verified, then open the payments page again.
## Rehearsing paid bookings before you go live
Before you hand a priced booking link to a real patient, you can run the whole thing yourself —
booking, checkout, confirmation, reminders, cancellation, refund — without any money moving. Put
the appointment type into **test mode**.
### Switching a type into test mode
Go to **Settings → Appointment types**, open **Pricing** for the type, choose **Edit pricing**, and
set **Payment mode** to *Test payments (no money is collected)*. Save.
Test mode is set on one appointment type at a time. Every other appointment type keeps its own
setting, so your live types carry on taking real payments while you rehearse with a dedicated test
type.
### It works while Mollie verification is still pending
Normally a priced booking link needs a Mollie account that Mollie has finished checking and enabled
for payments. A test-mode appointment type does not wait for that verdict — you can rehearse the
paid flow from day one, while your Mollie onboarding is still in progress.
**You still need a connected Mollie account.** Test mode waives Mollie's verification result and
nothing else. If your organisation has no Mollie connection at all, or the connection is
disconnected, revoked, or otherwise unusable, priced links are still refused — in the admin portal
when you try to generate one, and at the checkout step with an error the patient sees. Connect
Mollie first, then switch the type into test mode.
### What you and the patient will see
Test mode is deliberately loud. It appears at every point where somebody could mistake a rehearsal
for a real booking:
* A **Test mode** badge beside the appointment type, in the types list and on the type's own page.
* A notice in the booking-link dialog whenever you generate a **priced** link for that type — the
moment a link could be handed to a patient who is meant to pay. An unpriced link takes no payment
at all, so it carries no notice.
* A banner on the patient's payment step: *Test booking — you will not be charged.*
* A **Test payment** badge on the appointment's row in the appointments list, beside its status.
It follows the payment rather than the appointment type, and it is shown whatever state that
payment ended up in — including refunded, failed, cancelled and expired.
* A **Test mode** badge in the refund dialog, so an old test payment is never refunded in the
belief that real money is going back.
* Different wording in the cancel dialog. Cancelling a paid appointment normally warns you that the
whole remaining amount goes back to the patient; on a test payment that would be false, so the
warning says instead that nothing was taken and nothing comes back.
### No money moves
On a test payment:
* The patient is never charged, and nothing leaves their account.
* Nothing is paid out to your organisation, and there is nothing to reconcile.
* A refund repeats the flow with the payment provider but sends nothing back, because nothing was
ever taken.
**The RxScale platform fee is shown but never settled.** A test payment carries the same fee
figure a live one would, because the request sent to Mollie is deliberately identical — that is
what makes the rehearsal meaningful. No fee is actually kept, though, because no money moved. Read
the amount as what the fee *would* have been, not as something you were charged.
### A test payment stays a test payment
Every payment records the mode it was taken in, and that never changes afterwards. Switching the
appointment type back to live payments does **not** convert the test payments you already made:
each one stays a test payment in the appointment list, and refunding it refunds it in test mode —
the row keeps its **Test payment** badge and the refund dialog its **Test mode** badge, whatever
the appointment type is set to by then. Only bookings made *after* the switch take real money.
### Going live
Test mode does not expire and does not block anything, so nothing turns it off for you. When you
are satisfied with the rehearsal, go back to **Settings → Appointment types → Pricing → Edit
pricing** and set **Payment mode** back to *Live payments — the normal setting for a paid
appointment type*.
**An appointment type left in test mode collects nothing.** Patients can still book it and will
still reach a checkout page, but no money will ever arrive. Before you publish a priced booking
link, check that the appointment type does not carry the **Test mode** badge.
If you build your own booking front end instead of using the hosted one, the booking session tells
you which mode a booking is *about to be* taken in: see `billing.test_mode` in the
[Scheduling API reference](/api-reference/scheduling/appointments#billing). Once a payment exists,
read `testmode` on the payment itself instead. That one is fixed when the payment is created, while
`billing.test_mode` follows the appointment type's current setting — so it can change underneath a
patient who is still at the checkout page.
## Refunding a paid appointment
When an appointment was booked through a priced booking link, its row in the appointments list
carries a payment, and a **Refund** action appears beside **Cancel**. The action is only shown
when there is money that can still go back — an appointment nobody paid for, or one whose payment
failed or has already been refunded in full, has no refund action at all.
The refund dialog shows what the patient paid, what has already gone back, and what is still
refundable, together with every refund made against that payment so far. You can:
* **Refund in full** — the default. It sends back everything still refundable, computed at the
moment you confirm rather than at the moment the dialog opened.
* **Refund part of it** — enter an amount in euros, up to what is still refundable. You can do
this more than once; each partial refund is recorded separately.
Optionally add a reason. On some payment methods it is shown to the patient, so write it as
something the patient would understand.
**The RxScale platform fee is not refunded.** Mollie does not return application fees with a
refund. Your Mollie account sends the patient the full amount back and RxScale keeps its fee,
which means a full refund costs you that fee on top of the payment you are returning. The refund
dialog names the exact amount.
### Cancelling refunds automatically
**Cancelling a paid appointment refunds the whole remaining amount.** That happens however the
cancellation is made — from this interface, from the Management API, from the patient's own
cancel link, or from the cancel link in a reminder email. The cancel dialog warns you when the
appointment has a payment that will be sent back.
**A partial refund before cancelling does not let you keep a late-cancellation fee.** Cancelling
sends back the *remainder*, so the two together still return everything: on a €49.00 payment, a
€40.00 partial refund followed by a cancellation sends back the remaining €9.00 and the patient
has had all €49.00. Keeping part of a payment across a cancellation is not supported today. If
you need to retain a fee, do not cancel the appointment — refund only the part you are giving
back and leave the appointment as it is.
**Rebooking does not refund.** Moving an appointment to a new time keeps the same payment against
the same booking, so the patient is neither charged again nor refunded.
### Refunds RxScale requests for you
If a payment arrives after its slot has been taken by somebody else — rare, but possible while a
slow payment method settles — the appointment cannot be confirmed and the payment is flagged
`refund_required`. RxScale requests the refund automatically; you do not need to do anything, and
the refund appears in the dialog like any other.
### When a refund cannot be made
A refund has to leave from the Mollie account that took the money. Two situations stop that:
* **You disconnected and reconnected the same Mollie account.** Refunds keep working — RxScale
uses your current credentials for the same account.
* **You connected a *different* Mollie account.** The money is in an account RxScale can no longer
reach, and refunding from the new one would take it out of the wrong balance. The refund is
marked failed with that reason. Reconnect the original account and request the refund again.
A refund is also marked failed when the payment provider refuses it outright, or when the amount
no longer fits because another refund settled in between. In every case the refund row records
why, and you can request a new one once the cause is fixed.
# FAQ
Source: https://docs.rxscale.com/for-telemedicine-providers/faq
Frequently asked questions for telemedicine providers using RxScale
# Frequently Asked Questions
Find answers to common questions about using RxScale as a telemedicine provider.
## Integration and Setup
Setup time depends on the complexity of your integration. A basic integration with the Management API and webhooks can be set up in a few days. More complex integrations involving custom workflows, waiting rooms, and wallet passes may take a few weeks. Your RxScale account manager will guide you through the process.
Yes. RxScale provides a development environment at `https://api.rxscale-dev.com` where you can test your integration with test data. We recommend thorough testing in the development environment before switching to production.
RxScale provides the backend infrastructure for prescription and order management. You are responsible for the patient-facing experience (your shop or app). Your application interacts with RxScale through the Management API and webhooks.
Yes. You can operate multiple shops, each with its own product catalog and configuration. This is useful if you serve different markets or offer different product lines.
## Orders and Prescriptions
Review times depend on doctor availability and your organisation's configuration. In most cases, prescriptions are reviewed within a few hours. Your account manager can help you set up your organisation for optimal review times.
If a doctor declines a prescription, the order status is updated to "prescription declined" and the patient is notified. You will receive a webhook notification with the updated status. The patient may need to provide additional information or schedule a new consultation.
Orders can be cancelled at certain stages. Contact the support team or use the Management API to cancel orders when needed. Note that orders that have already been shipped by the pharmacy cannot be cancelled through the API.
RxScale automatically routes orders to pharmacies based on product availability, location, and capacity. You do not need to manage pharmacy assignments manually.
## Products and Inventory
Adding new products typically involves working with your account manager to set up the product details and associated medical questionnaire. Once the product is configured, you can manage its SKUs, pricing, and availability through the Management API.
Stock levels are managed by the pharmacies that fulfill your orders. When a pharmacy's stock for a product reaches zero, orders for that product will not be routed to that pharmacy. You can monitor stock levels through the Management API.
Yes. Pricing is managed at the SKU level and can be configured per shop. Contact your account manager to set up shop-specific pricing.
## Technical
RxScale will retry failed webhook deliveries. If your endpoint continues to be unavailable, events will be queued and retried later. Set up monitoring for your webhook endpoint so you can resolve issues quickly. See [Webhooks and Notifications](/for-telemedicine-providers/webhooks-and-notifications) for best practices.
Yes. The Management API has rate limits to ensure fair usage. See the [Rate Limits](/rate-limits) documentation for details.
Yes. The development environment at `https://api.rxscale-dev.com` serves as a sandbox for testing your integration without affecting production data.
For technical questions, contact your RxScale account manager or reach out to the support team. They can help with API issues, integration guidance, and troubleshooting.
# Getting Started
Source: https://docs.rxscale.com/for-telemedicine-providers/getting-started
Introduction to RxScale for telemedicine providers -- key concepts and setup
# Getting Started
This page introduces the key concepts of RxScale for telemedicine providers and guides you through setting up your integration.
## What is RxScale for Telemedicine Providers?
RxScale handles the prescription and order management workflow behind your telemedicine platform. When a patient needs medication through your service, RxScale takes care of:
* **Prescription review** -- Routing the prescription to a qualified doctor for review and approval.
* **Electronic signing** -- Managing the qualified electronic signature (QES) process for legal compliance.
* **Pharmacy fulfillment** -- Routing the signed prescription to a pharmacy for preparation and delivery.
* **Status tracking** -- Keeping all parties informed about order progress at every step.
You focus on the patient experience. RxScale handles everything behind the scenes.
## Key Concepts
Before you start, it helps to understand these core concepts:
### Shops
A shop is your patient-facing online presence. It is where patients browse products and place orders. You can have one or more shops, each with its own product catalog and configuration.
### Products
Products are the items available in your shop. In the context of telemedicine, products are typically medications or treatment plans that patients can request.
### SKUs (Stock Keeping Units)
SKUs are specific variants of a product. For example, a medication might come in different dosages or package sizes. Each variant has its own SKU with its own price and stock level.
### Orders
When a patient completes a checkout, an order is created. The order contains the products the patient requested and moves through the RxScale workflow: doctor review, prescription signing, pharmacy assignment, and fulfillment.
### Prescriptions
A prescription is the medical document associated with an order. It is created when a doctor reviews and approves the patient's medication request, and becomes legally valid once the doctor signs it with a qualified electronic signature.
## Setting Up Your Integration
Contact your RxScale account manager to receive your Management API credentials. These include an API key that you will use to authenticate your requests.
Work with your account manager to configure your shop in the RxScale system. This includes setting up your product catalog, pricing, and any custom questionnaires for patient intake.
Add products and SKUs to your catalog. Each product needs at least one SKU with pricing and stock information. See [Products and SKUs](/for-telemedicine-providers/products-and-skus) for details.
Register webhook endpoints to receive real-time notifications about order and prescription status changes. See [Webhooks and Notifications](/for-telemedicine-providers/webhooks-and-notifications) for details.
Use the development environment to test your integration before going live. The development base URL is `https://api.rxscale-dev.com`.
Once testing is complete, switch to the production environment at `https://api.rxscale.com` and start processing real orders.
## Understanding the Order Flow
Here is a simplified view of how an order flows through the system:
```
Patient places order in your shop
|
v
Order created in RxScale
|
v
Doctor reviews prescription
|
v
Doctor signs prescription (QES)
|
v
Order routed to pharmacy
|
v
Pharmacy prepares and ships
|
v
Patient receives medication
```
For a detailed explanation of every status and transition, see [Orders and Prescriptions](/for-telemedicine-providers/orders-and-prescriptions).
## Next Steps
* [Products and SKUs](/for-telemedicine-providers/products-and-skus) -- Set up your product catalog
* [Orders and Prescriptions](/for-telemedicine-providers/orders-and-prescriptions) -- Understand the full order lifecycle
* [Webhooks and Notifications](/for-telemedicine-providers/webhooks-and-notifications) -- Set up real-time notifications
* [Management API](/for-telemedicine-providers/management-api) -- Explore the API
# Management API
Source: https://docs.rxscale.com/for-telemedicine-providers/management-api
Overview of the Management API for telemedicine providers
# Management API
The Management API gives you programmatic access to your organisation's data in RxScale. This page provides an overview of what you can do with the API and common use cases.
## Overview
The Management API is designed for telemedicine providers who need to:
* Query and manage orders and prescriptions.
* Manage their product catalog and SKUs.
* Access doctor and patient information.
* Set up waiting rooms for video consultations.
* Create and manage wallet passes for patient verification.
* Register and manage webhooks.
All API requests require authentication using an API key in the `X-API-Key` header.
## Common Use Cases
### Tracking Orders
Use the orders endpoints to:
* List all orders for your organisation, with filters for status, date range, and more.
* Get detailed information about a specific order, including its current status, associated prescription, and pharmacy assignment.
* Monitor order progress alongside webhook notifications.
### Managing Prescriptions
Use the prescriptions endpoints to:
* View prescription details, including the doctor's decision and signing status.
* Track prescription status changes over time.
### Product Management
Use the products endpoints to:
* List your products and SKUs.
* Update product information.
* Manage pricing and availability.
### Doctors and Patient Management
Use the doctors and patients endpoints to:
* View which doctors are available in your organisation.
* Access patient records and prescription history.
* Manage patient data within your organisation.
### Waiting Room
Use the waiting room endpoints to:
* Register patients for video consultations.
* Manage the consultation queue.
* Connect patients with available doctors.
See [Waiting Room](/for-telemedicine-providers/waiting-room) for more details on this feature.
### Wallet Passes
Use the wallet passes endpoints to:
* Create digital wallet passes for patients.
* Send push notifications to patients through their wallet pass.
* Manage patient verification using QR codes.
See [Wallet Passes](/for-telemedicine-providers/wallet-passes) for more details on this feature.
## Getting Started with the API
Create an API key in the Admin Tool under **Settings** → **API Keys**. Select the permissions you need (e.g. `order:read`, `product:read`). See [Authentication](/authentication) for details.
All requests go to the production API at `https://api.rxscale.com`. Test access is provided through test stores — requests always go to production resources.
Try listing your orders to verify your API key is working:
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/orders" \
-H "X-API-Key: your-api-key-here"
```
See the full API reference for all available endpoints, request formats, and response schemas.
## Full API Reference
For complete documentation of all endpoints, request parameters, and response formats, see the [Management API Reference](/api-reference/management/overview).
## Related Topics
* [Management API Reference](/api-reference/management/overview) -- Full API documentation.
* [Authentication](/authentication) -- How API authentication works.
* [Rate Limits](/rate-limits) -- API rate limiting details.
* [Webhooks and Notifications](/for-telemedicine-providers/webhooks-and-notifications) -- Set up real-time notifications.
# Orders and Prescriptions
Source: https://docs.rxscale.com/for-telemedicine-providers/orders-and-prescriptions
How orders and prescriptions flow through the RxScale system
# Orders and Prescriptions
This page explains the complete lifecycle of an order in RxScale -- from the moment a patient places it to the moment they receive their medication.
## How Orders Flow Through the System
When a patient places an order through your shop, it triggers a series of steps managed by RxScale:
```
Patient places order
|
v
Order created (init)
|
v
Processing started (started)
|
v
Waiting for doctor review
|
v
Doctor approves prescription
|
v
Prescription signed (QES)
|
v
Order sent to pharmacy (waiting for pharmacy)
|
v
Pharmacy processes order
|
v
Order shipped
|
v
Order completed
```
## Order Lifecycle Step by Step
The patient completes a checkout in your shop. An order is created in RxScale with a status of "init". The system validates the order and begins processing.
The system verifies the order details, checks product availability, and prepares the prescription request for doctor review.
The prescription is placed in a doctor's review queue. The doctor reviews the patient's questionnaire responses and medical information.
The doctor either approves, declines, or places the prescription on hold.
* **Approved** -- The prescription moves to signing.
* **Declined** -- The order is updated and the patient is notified.
* **On hold** -- The prescription is paused for additional information.
The doctor electronically signs the prescription using a qualified electronic signature (QES). This makes it legally valid.
The system routes the order to an appropriate pharmacy based on product availability and location.
The pharmacy reviews the order, prepares the medication, and ships it to the patient.
The patient receives their medication and the order is marked as completed.
## Prescription Management
### Prescription Creation
A prescription is created automatically when an order enters the doctor review stage. You do not need to create prescriptions manually -- the system handles this based on the products in the order.
### Prescription Statuses
| Status | What It Means |
| -------------------- | --------------------------------------------------------------------- |
| `waiting for doctor` | A doctor has not yet reviewed this prescription. |
| `approved` | The doctor approved the prescription. It will be signed next. |
| `signed` | The prescription has been electronically signed and is legally valid. |
| `declined` | The doctor declined this prescription. |
| `ON_HOLD` | The prescription is on hold for additional review. |
| `cancelled` | The prescription was cancelled. |
For detailed status descriptions, see [Prescription Statuses](/help-center/prescription-statuses).
### Downloading a Prescription
Open an order in the Admin Tool and use the download menu in the **Prescriptions** section. Three
options are available once a prescription is signed:
| Option | What you get |
| -------------------------- | -------------------------------------------------------------- |
| Download prescription file | The prescription PDF exactly as it was signed. |
| Download prescription copy | The same PDF stamped with a diagonal `COPY / KOPIE` watermark. |
| Download as zip | The prescription plus any related documents, in one archive. |
Use the copy whenever the PDF is going to someone outside your organisation. The watermark makes
clear that the document is a duplicate and not the signed original.
If a prescription PDF cannot be watermarked -- for example because it is password-protected -- the
copy download fails and returns no file. This is deliberate: you will never receive an unmarked
original when you asked for a copy. Use **Download prescription file** if you need the original.
## Working with Pharmacy Partners
Once a prescription is signed, RxScale handles pharmacy assignment automatically. The system considers:
* **Product availability** -- Whether the pharmacy has the required products in stock.
* **Location** -- Geographic proximity to the patient for faster delivery.
* **Capacity** -- The pharmacy's current workload.
You do not need to manage pharmacy relationships directly. RxScale takes care of routing orders to the right pharmacy.
## Monitoring Orders
You can monitor order progress through:
* **Webhooks** -- Receive real-time notifications when order or prescription statuses change. See [Webhooks and Notifications](/for-telemedicine-providers/webhooks-and-notifications).
* **Management API** -- Query order and prescription details programmatically. See [Management API](/for-telemedicine-providers/management-api).
## Shopify Priority Settings
If your shop uses Shopify, you can pass priority values to RxScale so urgent patients or orders are surfaced ahead of normal-priority work. Use integer values; higher numbers mean higher priority. Missing or invalid values are ignored and the default priority is used.
| Setting | Shopify location | Required key | Example value |
| ---------------- | --------------------------------------------- | ------------------------------------------ | ------------- |
| Order priority | Order additional attribute / custom attribute | `_rxscale_priority` | `10` |
| Patient priority | Customer metafield | namespace `custom`, key `rxscale_priority` | `5` |
Order priority applies to the specific Shopify order. Patient priority is stored on the patient profile and can be reused across the patient's future activity.
Store priority values as plain integers. Values such as `high`, `urgent`, or empty strings are ignored instead of being converted to a priority.
## Placing Prescriptions on Hold
If your shop uses Shopify, you can have a prescription created **on hold** instead of going straight to the doctor. Held prescriptions are not offered to doctors for review until they are taken off hold, which is useful when an order needs a manual check first (for example, awaiting a lab result or an identity confirmation).
Set the hold intent through order attributes:
| Setting | Shopify location | Required key | Example value |
| ------------------------ | --------------------------------------------- | ------------------------------------ | --------------------- |
| Put prescription on hold | Order additional attribute / custom attribute | `_rxscale_prescription_hold` | `true` |
| Hold reason (optional) | Order additional attribute / custom attribute | `_rxscale_prescription_hold_comment` | `Awaiting lab result` |
* The prescription is placed on hold only when `_rxscale_prescription_hold` is a truthy value: `true`, `1`, or `yes` (case-insensitive). Any other value — or a missing attribute — creates the prescription normally.
* `_rxscale_prescription_hold_comment` is optional. When present alongside a truthy hold, the reason is recorded on the prescription's status history and shown to the reviewer. The comment is ignored if the order is not held.
* The hold is applied when the prescription is first created. Adding the attribute to an existing order later does not retroactively hold an already-created prescription.
The hold applies to the prescription only. The order itself continues through its normal lifecycle.
## Confirming Patient-Doctor Meetings on Payment
If your shop uses Shopify and the storefront books a patient-doctor meeting before checkout, you can automatically confirm that meeting when the order is **paid**. Set the meeting UID on the order and/or line item:
| Setting | Shopify location | Required key | Example value |
| ------------------------------ | ----------------------------------------------------------------------- | ---------------------- | ------------- |
| Confirm patient-doctor meeting | Order additional attribute / custom attribute and/or line item property | `_rxscale_meeting_uid` | `pdm_abc123` |
* Confirmation runs only when the order financial status is paid.
* You may attach one UID per attribute occurrence (for example one on the order and one per line item). Duplicate values are confirmed once.
* Missing, already confirmed, cancelled, or otherwise non-confirmable UIDs are skipped without failing order ingest.
* A successful confirm emits the same meeting-updated webhook events as confirming the hold through the scheduling API.
## Skipping RxScale Order Import
If your shop uses Shopify, you can prevent RxScale from automatically importing an order via Shopify webhooks (and from processing later webhook updates or cancellations for that order) by setting an order additional attribute:
| Setting | Shopify location | Required key | Example value |
| ----------------------- | --------------------------------------------- | ------------------------------------------------- | ------------- |
| Ignore order in RxScale | Order additional attribute / custom attribute | `rxscale_ignore_order` or `_rxscale_ignore_order` | `true` |
* RxScale skips the order only when the attribute value is `true` (case-insensitive). Values such as `1`, `yes`, `false`, or a missing attribute do **not** skip import.
* While the attribute is set, automatic Shopify webhook processing does not import, update, or cancel-process the order in RxScale. An order that was already imported earlier is left as-is; further Shopify webhooks for that order are ignored until the attribute is removed or no longer `true`.
This is different from `_skip_validation` / `_rxscale_skip_validation`, which still import the order but skip anamnesis validation for prescription items.
## Age Limit for Shopify Order Webhooks
RxScale ignores Shopify order webhooks for orders created more than **60 days** before the webhook was sent. This keeps bulk edits, tag sweeps, and archive migrations of long-closed orders from re-entering the RxScale pipeline.
* The limit applies to order creation and order update webhooks.
* Order cancellations are always processed, however old the order is.
* The age is measured from the order's creation date in Shopify to the moment the webhook was sent — not from the date you edit the order. Editing a two-year-old order therefore has no effect in RxScale.
To get a change into RxScale for an order past the limit, create a new order. If an older order genuinely needs to be imported, contact RxScale support. An order that already exists in RxScale with no fulfillment records cannot be imported again — it was already dispatched by the previous system, and re-importing it would create a duplicate prescription. This does not apply to an order RxScale never imported in the first place; a genuinely old order like that can still be brought in normally.
## Pharmacy Orders in the API
When you retrieve orders via the Management API, each order includes a `pharmacy_orders` array showing how the order is being fulfilled:
```json theme={null}
{
"uid": "order-123",
"status": "started",
"pharmacy_orders": [
{
"uid": "po-456",
"status": "waiting for pharmacy",
"external_status": null,
"name": "PO-A1B2-C3D4",
"pharmacy": {
"uid": "pharm-789",
"display_name": "City Pharmacy"
},
"delivery_type": {
"uid": "dt-001",
"display_name": "Standard Shipping",
"identifier": "standard_shipping"
},
"created_at": 1712000000,
"updated_at": 1712000100
}
]
}
```
Each pharmacy order represents a fulfillment attempt by a pharmacy. An order may have multiple pharmacy orders if it was reassigned to a different pharmacy.
`delivery_type` is resolved from the connected shop's fulfillment method (configured per shop), otherwise from the receiving pharmacy's default delivery type. It may be `null` when neither is set, so do not assume a delivery type is always present.
### Pharmacy Order Statuses
| Status | What It Means |
| ---------------------- | --------------------------------------------------------------- |
| `init` | Pharmacy order has been created but not yet sent. |
| `waiting for pharmacy` | Order has been sent to the pharmacy and is awaiting processing. |
| `in-progress` | The pharmacy is actively processing the order. |
| `completed` | The pharmacy has shipped the order. |
| `cancelled` | The pharmacy order was cancelled. |
## Related Topics
* [Order Statuses](/help-center/order-statuses) -- Detailed explanation of all order statuses.
* [Prescription Statuses](/help-center/prescription-statuses) -- All prescription statuses and their meanings.
* [Order Lifecycle](/guides/order-lifecycle) -- Technical guide to order status transitions.
# Telemedicine Provider Guide
Source: https://docs.rxscale.com/for-telemedicine-providers/overview
Your guide to using RxScale as a telemedicine provider
# Welcome to RxScale for Telemedicine Providers
This guide helps telemedicine providers understand and use the RxScale platform. Whether you are setting up your first integration or managing an existing one, you will find clear instructions here for every step.
## What is RxScale?
RxScale is a digital platform that manages the entire prescription workflow -- from patient checkout to doctor review to pharmacy fulfillment. As a telemedicine provider, you use RxScale to power the prescription and order management behind your patient-facing services.
## What Can You Do with RxScale?
Manage your product catalog, set up SKUs, and link products to your shops.
Understand how orders flow through the system from checkout to fulfillment.
Set up real-time notifications to stay informed about order and prescription updates.
Access your data programmatically to build custom workflows and integrations.
Create digital wallet passes for patient identity verification.
Manage virtual waiting rooms for video consultations with patients.
## Getting Started
If you are new to RxScale, start with the [Getting Started](/for-telemedicine-providers/getting-started) guide to understand the key concepts and how to set up your integration.
## Help Center
For detailed explanations of statuses, workflows, and platform concepts, visit the [Help Center](/help-center/overview):
* [Order Statuses](/help-center/order-statuses) — What each order status means
* [Prescription Statuses](/help-center/prescription-statuses) — Review and signing workflow stages
* [Pharmacy Order Statuses](/help-center/pharmacy-order-statuses) — Pharmacy-side order tracking
* [Fulfillment Statuses](/help-center/fulfillment-statuses) — Fulfillment workflow stages
* [Signing Process](/help-center/signing) — How qualified electronic signatures work
* [Delivery Types](/help-center/delivery-types) — Available delivery options
* [Wallet Passes](/help-center/wallet-passes) — Patient identity verification
* [Notifications](/help-center/notifications) — How webhook notifications work
## Need Help?
If you have questions that are not covered in this guide, contact your RxScale account manager or reach out to our support team. You can also check the [FAQ](/for-telemedicine-providers/faq) for answers to common questions.
# Pharmacy invites
Source: https://docs.rxscale.com/for-telemedicine-providers/pharmacy-invites
Onboard a pharmacy into your organisation by sharing a single-use invite link.
# Pharmacy invites
Pharmacy invites let admins onboard a pharmacy without RxScale having to
provision it manually. You mint a link in the admin tool, share it via your
own channel (email, WhatsApp, whatever the pharmacy already uses), and the
pharmacist completes the rest on their own.
## Mint an invite
1. Open **Settings → Pharmacy invites**.
2. Click **Create invite**. A note field is optional but helps you
recognise which invite went to which pharmacy.
3. Submit. The next screen shows the **invite link in plaintext**. Copy it
immediately — closing the dialog clears it from the page. (You can still
revoke and reissue if you lose the link.)
The link expires after **7 days** and burns the moment the pharmacy
finishes onboarding.
## Share the invite
RxScale does **not** send the link. Send it through whatever channel you
already use with that pharmacy. Anyone holding the link can redeem it — so
share through a trusted channel.
## Manage existing invites
The list shows every invite you have minted. The status badge tells you
where it is:
* **Active** — link still works.
* **Consumed** — the pharmacy onboarded successfully.
* **Expired** — the 7-day window has passed.
* **Revoked** — you cancelled it.
Use **Revoke** on any active row to kill the link immediately.
## What the pharmacist sees
See [Joining RxScale](/for-pharmacists/joining-rxscale) for the
pharmacist-side walkthrough.
# Products and SKUs
Source: https://docs.rxscale.com/for-telemedicine-providers/products-and-skus
How to manage your product catalog, SKUs, and pricing in RxScale
# Products and SKUs
Your product catalog is the foundation of your RxScale integration. This page explains how products and SKUs work and how to manage them.
## Understanding Products and SKUs
### Products
A product represents a medication or treatment that patients can request through your shop. Each product has:
* **Name** -- The display name patients will see.
* **Description** -- Information about the product.
* **Category** -- How the product is classified.
* **Associated questionnaire** -- The medical questionnaire patients must complete when requesting this product.
### SKUs (Stock Keeping Units)
Each product can have one or more SKUs. A SKU represents a specific variant of a product. For example:
| Product | SKU Variant | SKU |
| ------------ | ---------------- | ----------- |
| Medication A | 30 tablets, 10mg | MED-A-30-10 |
| Medication A | 60 tablets, 10mg | MED-A-60-10 |
| Medication A | 30 tablets, 20mg | MED-A-30-20 |
Each SKU has its own price, stock level, and availability settings.
## Managing Your Product Catalog
You can manage your products and SKUs through the Management API or by working with your RxScale account manager. Common tasks include:
### Adding Products
New products are typically set up during your initial onboarding. To add products later:
1. Define the product details (name, description, category).
2. Create the associated medical questionnaire if one does not already exist.
3. Add one or more SKUs with pricing and stock information.
4. Link the product to your shop.
Adding new products usually requires coordination with your account manager, especially when a new medical questionnaire needs to be created.
### Managing SKU Variants
You can add, update, or deactivate SKU variants for existing products. When managing SKUs, keep in mind:
* Each SKU must have a unique identifier.
* Price and stock levels are managed per SKU.
* Deactivating a SKU removes it from your shop without deleting historical data.
## Linking Products to Shops
Products need to be linked to a shop before patients can see and order them. If you operate multiple shops, each shop can have its own selection of products.
* A product can be linked to multiple shops.
* Each shop can display different products based on its target audience or region.
* Product availability is controlled at the SKU level, so a product can be available in one shop but out of stock in another.
## Price and Stock Management
### Pricing
Prices are set per SKU. When updating prices:
* Price changes apply to new orders immediately.
* Existing orders are not affected by price changes.
* Make sure your prices are consistent with your shop's display to avoid confusion.
### Stock Levels
Stock levels determine whether a product can be ordered. The stock for each SKU is managed by the pharmacy that fulfills orders for that product.
* When stock reaches zero, the SKU is marked as out of stock and cannot be ordered.
* Stock is automatically reduced when orders are completed.
* Pharmacies can update their stock levels through the Pharmacy Portal or API.
## Doctor SKU Blacklist
The Doctor SKU Blacklist allows you to restrict specific SKUs from being prescribed by individual doctors. This is useful for:
* **Specialization restrictions** -- Limiting doctors to SKUs within their area of expertise.
* **Licensing requirements** -- Ensuring doctors only prescribe medications they are licensed for.
* **Compliance** -- Enforcing organizational or regulatory restrictions on a per-doctor basis.
### How It Works
When a SKU is blacklisted for a doctor, that doctor will no longer be able to prescribe it. The blacklist operates at the SKU level (not the product level), giving you fine-grained control over which specific variants a doctor can prescribe.
Blacklisting a SKU does not affect other doctors. Each doctor has their own independent blacklist.
### Managing the Blacklist
The SKU blacklist is managed through the Admin Portal under the doctor detail page:
1. Navigate to **Doctors** in the Admin Portal.
2. Select the doctor you want to manage.
3. Scroll to the **Blacklisted SKUs** section.
4. Use the **Add** button to select one or more SKUs to blacklist in one step. SKUs that are already on this doctor's blacklist stay visible but cannot be selected again.
5. Use the **Remove** button on any entry to unblock a SKU.
You can search the blacklist by SKU name, product name, or PZN to quickly find specific entries.
### API Access
You can also manage the blacklist programmatically via the Admin API:
```bash theme={null}
# List blacklisted SKUs for a doctor
curl -X GET "https://api.rxscale.com/v1/admin/doctors/{doctor_uid}/blacklisted-skus" \
-H "Authorization: Bearer {token}"
# Add a SKU to the blacklist
curl -X POST "https://api.rxscale.com/v1/admin/doctors/{doctor_uid}/blacklisted-skus" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"sku_uid": "sku-uid-here"}'
# Remove a SKU from the blacklist
curl -X DELETE "https://api.rxscale.com/v1/admin/doctors/{doctor_uid}/blacklisted-skus/{sku_uid}" \
-H "Authorization: Bearer {token}"
```
The list endpoint supports pagination (`page`, `limit`) and search (`search`) query parameters.
## Related Topics
* [Orders and Prescriptions](/for-telemedicine-providers/orders-and-prescriptions) -- How orders flow through the system.
* [Management API](/for-telemedicine-providers/management-api) -- Manage products programmatically.
* [Management API Products Reference](/api-reference/management/products) -- Full API documentation for product endpoints.
# Waiting Room
Source: https://docs.rxscale.com/for-telemedicine-providers/waiting-room
How to use the virtual waiting room for patient consultations
# Waiting Room
The virtual waiting room lets you manage patient queues for video consultations. Patients can register for a consultation and wait in a queue until a doctor is available to see them.
## How the Waiting Room Works
The waiting room is a virtual queue system that connects patients with doctors for video consultations. Here is the basic flow:
```
Patient registers for consultation
|
v
Patient enters waiting room
|
v
Patient waits in queue
|
v
Doctor picks up patient
|
v
Video consultation begins
```
## Registering Patients
Patients can be registered for a waiting room consultation in two ways:
* **Self-registration** -- Patients register themselves through your patient-facing application.
* **API registration** -- You register patients programmatically using the Management API.
When a patient is registered, they are placed in the waiting room queue. They can see their position in the queue and estimated wait time.
## Queue Management
The waiting room queue is managed on a first-come, first-served basis. As a telemedicine provider, you can:
* **View the queue** -- See all patients currently waiting for a consultation.
* **Monitor wait times** -- Track how long patients have been waiting.
* **Manage capacity** -- Control the number of doctors available to take consultations.
Doctors pick up patients from the queue through their Doctor Portal. When a doctor selects a patient, the video consultation starts immediately.
## Video Consultation Integration
The waiting room integrates directly with the video meeting feature in the Doctor Portal. Once a doctor picks up a patient from the queue:
1. The video call is initiated automatically.
2. Both the patient and doctor are connected.
3. The doctor can conduct the consultation, review medical history, and make prescription decisions.
4. After the consultation, the doctor can proceed with prescription review and approval if needed.
The video consultation feature requires patients to have a modern web browser and grant camera and microphone permissions.
## Setting Up the Waiting Room
To set up the waiting room for your organisation:
Work with your RxScale account manager to enable and configure the waiting room feature for your organisation.
Ensure that doctors in your organisation are available to pick up consultations from the queue. This is managed through the Doctor Portal.
Use the Management API to register patients for consultations programmatically, or set up self-registration in your patient-facing application.
Track queue metrics like average wait time and consultation duration to optimise your staffing and scheduling.
## Best Practices
Ensure enough doctors are available to handle the queue during peak hours. Long wait times can frustrate patients and lead to drop-offs.
Let patients know their estimated wait time when they enter the queue. Clear communication reduces frustration.
If possible, encourage patients to schedule consultations during off-peak hours to reduce wait times for everyone.
## Related Topics
* [Management API Waiting Room](/api-reference/management/waiting-room) -- API reference for waiting room endpoints.
* [Video Meetings (For Doctors)](/for-doctors/video-meetings) -- How doctors join video consultations.
# Wallet Passes
Source: https://docs.rxscale.com/for-telemedicine-providers/wallet-passes
How to create and manage digital wallet passes for patient verification
# Wallet Passes
Wallet passes are digital cards that patients carry on their phones for identity verification. This page explains how telemedicine providers can use wallet passes in their workflow.
## What Are Wallet Passes?
A wallet pass is a digital card stored in a patient's Apple Wallet or Google Wallet app. It contains a QR code that can be scanned to verify the patient's identity. Think of it as a digital membership card that is always available on the patient's phone.
Wallet passes are useful for:
* **Patient verification** -- Confirming a patient's identity at a pharmacy or during a consultation.
* **Quick identification** -- Allowing staff to look up a patient's information by scanning their QR code.
* **Push notifications** -- Sending updates to patients directly through their wallet pass.
## Creating Wallet Passes
You can create wallet passes for your patients using the Management API. When you create a wallet pass:
1. The system generates a unique wallet pass for the patient.
2. The patient receives a link to add the pass to their phone.
3. Once added, the pass is stored in their Apple Wallet or Google Wallet.
Patients need to actively add the pass to their phone. The pass is not installed automatically -- they will receive a link and need to tap it to add it.
Pass content is defined by the wallet pass template. When mapped, RxScale fills the
patient's date of birth and the date of their first signed or non-QES-signed
prescription (patient since), in addition to name and latest-prescription fields.
## Managing Wallet Passes via API
The Management API provides full CRUD access for wallet passes. All endpoints require an API key with the appropriate permissions (`wallet_pass:read`, `wallet_pass:write`).
### Create or Update a Wallet Pass
Use the create endpoint to issue a new wallet pass for a patient. If a wallet pass already exists for the same template and patient, it will be updated instead of creating a duplicate.
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/wallet-passes" \
-H "X-API-Key: {api_key}" \
-H "Content-Type: application/json" \
-d '{
"wallet_pass_template_uid": "wpt-abc123",
"shop_customer_id": "cust-123",
"shop_identifier": "my-shop"
}'
```
**Response** (`201 Created` for new passes, `200 OK` for updates):
```json theme={null}
{
"uid": "wp-abc123",
"wallet_pass_template_uid": "wpt-abc123",
"external_id": "ext-123",
"serial_number": "SN-123",
"ios_download_url": "https://...",
"android_download_url": "https://...",
"patient_profile_uid": "pp-abc123",
"created_at": 1712000000
}
```
The response includes download URLs for both iOS and Android. Share these links with your patient so they can add the pass to their phone.
The create endpoint uses **upsert** semantics. You can safely call it multiple times for the same patient and template without creating duplicate passes.
Templates that display prescription details are issued after the patient has a signed
prescription. Identity-only templates can be issued earlier. In the admin tool under
**Settings → Wallet passes**, you can mark a template so it still waits for a signed prescription
even when it does not display prescription details.
When issuance is deferred for this reason, the create call returns `400` with a specific message
rather than a generic error:
```json theme={null}
{
"error": "The wallet pass template requires a signed prescription, but this patient has none."
}
```
This is a retryable state, not a failure of your request. Call the endpoint again once the patient
has a signed prescription; the upsert semantics above make the retry safe. For templates mapped to
a SKU, the pass is issued automatically when the prescription is signed, so no retry is needed.
### Get a Wallet Pass
Retrieve details for a specific wallet pass by its UID:
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/wallet-passes/{wallet_pass_uid}" \
-H "X-API-Key: {api_key}"
```
### List Wallet Passes
List all wallet passes for a specific customer:
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/wallet-passes?shop_customer_id=cust-123&shop_identifier=my-shop" \
-H "X-API-Key: {api_key}"
```
You can optionally filter by template using the `wallet_pass_template_uid` query parameter.
### Delete a Wallet Pass
Revoke a wallet pass when it is no longer needed:
```bash theme={null}
curl -X DELETE "https://api.rxscale.com/v1/management/wallet-passes/{wallet_pass_uid}" \
-H "X-API-Key: {api_key}"
```
Returns `204 No Content` on success. The pass is removed from the external wallet pass provider and will no longer be valid on the patient's phone.
### Verify a Scanned Pass
Scanning a Patient Pass yields its `wallet_pass_uid`. Post it to resolve the pass to the patient behind it. Requires an API key with the `wallet_pass:verify` permission.
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/wallet-passes/verify" \
-H "X-API-Key: {api_key}" \
-H "Content-Type: application/json" \
-d '{"wallet_pass_uid": "3f9a1c2e-7b04-4c11-9f3d-2a8e5b6c0d17"}'
```
```json theme={null}
{
"valid": true,
"status": "active",
"wallet_pass_uid": "3f9a1c2e-7b04-4c11-9f3d-2a8e5b6c0d17",
"patient_profile_uid": "b21d4f8a-5c33-4e90-8a72-1f6b9d0c4e55",
"shop_customer_id": "cust-123"
}
```
A revoked pass returns `"valid": false` with `"status": "revoked"`, so you can tell a cancelled pass from one that is not yours. A pass belonging to another organisation returns `404`, exactly like an unknown value.
Treat the `wallet_pass_uid` as a credential -- anyone holding it can resolve the patient behind the pass. Send it in the request body, never in a URL, and do not write it to logs.
This endpoint returns identity handles only. Load the patient's details with `GET /v1/management/patients`.
### List Templates
List available wallet pass templates for a shop:
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/wallet-passes/templates?shop_identifier=my-shop" \
-H "X-API-Key: {api_key}"
```
### Send Push Notifications
Send push notifications to patients through their wallet passes:
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/wallet-passes/push-notifications" \
-H "X-API-Key: {api_key}" \
-H "Content-Type: application/json" \
-d '[
{
"wallet_pass_uid": "wp-abc123",
"message": "Your prescription has been signed."
}
]'
```
When you update a pass, the changes are automatically pushed to the patient's phone. They do not need to take any action to see the updated information.
### Automatic Refresh on Patient Profile Changes
When a patient's profile is updated (e.g. name change after marriage), all existing wallet passes for that patient are automatically refreshed. The updated data is pushed to the wallet pass provider so the patient's pass always shows current information.
## Push Notifications
One of the most powerful features of wallet passes is the ability to send push notifications to patients. When a patient has your wallet pass on their phone, you can send them notifications that appear on their lock screen.
Common uses for push notifications:
* Notifying patients that their prescription has been signed.
* Alerting patients that their order has shipped.
* Reminding patients about upcoming consultations.
Push notifications are sent through the wallet pass platform, so patients receive them even if they are not actively using your app.
## Patient Verification via QR Code
The QR code on a wallet pass enables a secure identity verification process:
The patient opens their wallet app and displays the QR code on their pass.
A pharmacy staff member or other authorised person scans the QR code using the RxScale system.
The system sends a one-time password (OTP) to the patient's email address, and also by SMS to the phone number registered in their patient profile when one is on file.
The patient provides the OTP to the staff member. Once verified, the patient's identity is confirmed.
This two-step verification (QR code plus OTP) ensures that only the actual patient can complete the verification, even if someone else has a copy of the QR code.
## Related Topics
* [Wallet Passes (Help Center)](/help-center/wallet-passes) -- General information about how wallet passes work.
* [Management API Wallet Passes](/api-reference/management/wallet-passes) -- API reference for wallet pass endpoints.
# Webhooks and Notifications
Source: https://docs.rxscale.com/for-telemedicine-providers/webhooks-and-notifications
How to set up webhook notifications for real-time order and prescription updates
# Webhooks and Notifications
Webhooks let RxScale notify your system in real time when something happens -- such as an order status change, a prescription being signed, or a pharmacy shipping an order. This page explains how to set up and use webhooks effectively.
## Setting Up Webhooks
To start receiving webhook notifications:
Create an HTTP endpoint on your server that can receive POST requests. This endpoint should be publicly accessible and able to handle incoming webhook payloads.
Use the Management API to register your webhook endpoint URL. You will specify which event types you want to receive.
RxScale will send a test event to your endpoint. Make sure your server responds with a 200 status code to confirm it is working.
Once registered, your endpoint will receive webhook notifications for the event types you subscribed to.
## Available Event Types
RxScale sends webhook notifications for a variety of events. Here are the most common ones:
| Event Type | When It Fires |
| --------------------------- | ------------------------------------------------------------------------------------------------- |
| Order created | A new order has been placed. |
| Order updated | An order's status has changed (for example, moved to "waiting for pharmacy"). |
| Prescription approved | A doctor has approved a prescription. |
| Prescription signed | A prescription has been electronically signed. |
| Prescription declined | A doctor has declined a prescription. |
| Prescription doctor changed | The doctor assigned to a prescription has changed. |
| Pharmacy order updated | A pharmacy has updated an order's status or added shipments (for example, shipped with tracking). |
For a complete list of event types and their payload formats, see the [Webhook Events](/webhooks/events) documentation.
### Prescription doctor assignment changes
If you subscribe to Pub/Sub notifications, RxScale can publish a dedicated event whenever the doctor assigned to a prescription changes. Subscribe to the notification type `PRESCRIPTION_DOCTOR_CHANGED` to receive these updates.
The payload uses the prescription notification envelope with `category` set to `prescription` and `event` set to `doctor_changed`:
```json theme={null}
{
"category": "prescription",
"event": "doctor_changed",
"data": {
"shop_identifier": "telemedicine-shop",
"external_order_id": "order-100045",
"external_fulfillment_id": "fulfillment-100045-1",
"external_provider_identifier": "rxscale",
"doctor_assignment_reason": "ADMIN_ASSIGNED",
"old_doctor_uid": null,
"new_doctor_uid": "doctor_123",
"prescription": {
"uid": "prescription_123",
"created_at": "2026-05-21T14:30:00Z",
"status": "pending",
"doctor": {
"uid": "doctor_123",
"first_name": "Maria",
"last_name": "Schneider"
},
"patient_data": {
"first_name": "Alex",
"last_name": "Muster",
"date_of_birth": "1988-04-12"
},
"documents": [
{
"uid": "document_123",
"type": "prescription",
"status": "available"
}
],
"items": [
{
"uid": "prescription_item_123",
"shop_sku_uid": "shop_sku_123",
"fulfillment_line_item_external_id": "line-item-1"
}
]
}
}
}
```
The `data` object contains the public prescription payload plus the fields `external_provider_identifier`, `doctor_assignment_reason`, `old_doctor_uid`, and `new_doctor_uid`. `old_doctor_uid` is `null` when a prescription was not assigned to a doctor before the change. `new_doctor_uid` is `null` when the assignment was removed.
`doctor_assignment_reason` describes why RxScale changed the assignment. Stable reason examples include `ADMIN_ASSIGNED`, `ORDER_ATTRIBUTE_ASSIGNED`, `QUEUE_ASSIGNED`, `PRESCRIPTION_REQUEUED`, `DOCTOR_REQUEUED_PRESCRIPTIONS`, `STALE_ASSIGNMENT_CLEARED`, and `ON_HOLD_DOCTOR_REMOVED`.
## Monitoring Order Status Changes
Webhooks are the best way to stay informed about what is happening with your orders. Instead of polling the API repeatedly, you receive a notification the moment something changes.
A typical monitoring workflow:
1. A patient places an order on your shop.
2. You receive a webhook when the order is created.
3. You receive a webhook when the doctor approves the prescription.
4. You receive a webhook when the prescription is signed.
5. You receive a webhook when the order is sent to a pharmacy.
6. You receive a webhook when the pharmacy ships the order.
7. You receive a webhook when the order is completed.
Each notification includes the relevant order or prescription data, so you can update your systems and inform your patients at every step.
## Best Practices for Webhook Handling
Your endpoint should respond with a 200 status code as quickly as possible. Process the webhook payload asynchronously if needed -- do not block the response while performing lengthy operations.
In rare cases, you may receive the same webhook event more than once. Design your handler to be idempotent, meaning processing the same event twice should not cause issues.
RxScale signs webhook payloads so you can verify they are authentic. Always verify the signature before processing the payload. See [Webhook Security](/webhooks/security) for details.
If your endpoint is down or returning errors, webhook deliveries will be retried. Set up monitoring to detect when your endpoint is failing so you can fix issues quickly.
Keep a log of all received webhook events. This is invaluable for debugging and understanding the sequence of events for any given order.
## Related Topics
* [Webhooks Overview](/webhooks/overview) -- Technical details about how webhooks work.
* [Webhook Events](/webhooks/events) -- Complete list of event types and payload formats.
* [Webhook Security](/webhooks/security) -- How to verify webhook signatures.
# Order Lifecycle
Source: https://docs.rxscale.com/guides/order-lifecycle
Understanding order status transitions in RxScale
# Order Lifecycle
Every order in RxScale moves through a series of statuses as it progresses from creation to completion. Understanding these statuses is essential for building a reliable integration.
## Status Flow
```
init
|
v
waiting for pharmacy -----> cancelled
|
v
pending review ----------> cancelled
|
v
in-progress
|
v
ready_for_pickup
|
v
completed
```
## Status Descriptions
| Status | Description | Who sets it |
| ---------------------- | -------------------------------------------------------------------------- | -------------------------------- |
| `init` | Order has been created in the system but is not yet assigned to a pharmacy | System |
| `waiting for pharmacy` | Order is assigned to a pharmacy and ready to be processed | System |
| `pending review` | Pharmacy is reviewing the order and checking availability | Pharmacy (via API) |
| `in-progress` | Pharmacy has started preparation | Pharmacy (via API) |
| `ready_for_pickup` | Order is packed and ready for pickup or shipping | Pharmacy (via API) |
| `completed` | Order has been delivered and finalized (includes stock reduction) | Pharmacy (via complete endpoint) |
| `cancelled` | Order was cancelled before completion | System or Pharmacy |
## Transitions
### Waiting for Pharmacy to Pending Review
When your pharmacy receives a new order (status: `waiting for pharmacy`), acknowledge that review has started:
```bash theme={null}
curl -X PATCH "https://api.rxscale.com/v1/external_pharmacy_api/pharmacy_orders/po-abc123/status" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{"status": "pending review"}'
```
### Pending Review to In Progress
Once stock availability is confirmed and preparation begins, update the status to `in-progress`:
```bash theme={null}
curl -X PATCH "https://api.rxscale.com/v1/external_pharmacy_api/pharmacy_orders/po-abc123/status" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{"status": "in-progress"}'
```
### In Progress to Ready for Pickup
Once the order has been packed and is ready for pickup or shipping, update the status to `ready_for_pickup`:
```bash theme={null}
curl -X PATCH "https://api.rxscale.com/v1/external_pharmacy_api/pharmacy_orders/po-abc123/status" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{"status": "ready_for_pickup"}'
```
### Ready for Pickup to Completed
When the order has been delivered or otherwise fulfilled, complete it through the dedicated complete order endpoint. This step includes stock reduction and order finalization.
```bash theme={null}
curl -X PATCH "https://api.rxscale.com/v1/external_pharmacy_api/pharmacy_orders/po-abc123/complete_order" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"tracking_links": [
{
"tracking_link": "https://tracking.example.com/parcel/123",
"carrier": "DHL"
}
]
}'
```
Do not set `completed` through the generic status endpoint. Use `complete_order` so RxScale can reduce stock and emit shipment and order update events consistently.
### Cancellation
An order can be cancelled before completion:
```bash theme={null}
curl -X PATCH "https://api.rxscale.com/v1/external_pharmacy_api/pharmacy_orders/po-abc123/status" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{"status": "cancelled"}'
```
## Webhook Notifications
You will receive webhook notifications for status changes and for shipment updates if you have subscribed to the `pharmacy_order_updated` event type. Each notification includes the full order data with the current status and `data.shipments` (parcels the pharmacy has recorded, including tracking URLs when available).
See [Webhook Events](/webhooks/events#shipments) for payload details, including the shipment field reference.
## Best Practices
Move orders from `waiting for pharmacy` to `pending review` as soon as your team begins reviewing them. This provides visibility to all stakeholders.
Keep the status current. Accurate status tracking helps with customer communication, reporting, and issue resolution.
Listen for `pharmacy_order_updated` webhooks with `cancelled` status. If a cancellation arrives while you are preparing an order, stop processing and update your internal systems accordingly.
Always follow the order: `waiting for pharmacy` to `pending review` to `in-progress` to `ready_for_pickup`. Use `complete_order` only when the order is fulfilled.
# Pharmacy Integration Guide
Source: https://docs.rxscale.com/guides/pharmacy-integration
Step-by-step guide for pharmacies to integrate with RxScale
# Pharmacy Integration Guide
This guide walks you through the steps to integrate your pharmacy or pharmacy management system with RxScale, from receiving your API key to processing your first order.
## Step 1: Get Your API Key
Contact your RxScale account manager to receive your API credentials. You will get:
* An **API key** for authentication
* A **pharmacy UID** (or group-level access if you manage multiple pharmacies)
* Access to the **development environment** for testing
| Environment | Base URL |
| ----------- | ----------------------------- |
| Development | `https://api.rxscale-dev.com` |
| Production | `https://api.rxscale.com` |
Test your key with a health check:
```bash theme={null}
curl -X GET "https://api.rxscale-dev.com/v1/external_pharmacy_api/health/" \
-H "X-API-Key: your-api-key-here"
```
## Step 2: Sync Your SKUs
Retrieve your pharmacy's SKU catalog and sync it with your internal product system.
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/external_pharmacy_api/pharmacy_skus/" \
-H "X-API-Key: your-api-key-here"
```
For each SKU, you can:
* **Set your price** (in euro cents)
* **Update stock levels** to reflect your current inventory
* **Link an external ID** from your pharmacy management system
```bash theme={null}
curl -X PATCH "https://api.rxscale.com/v1/external_pharmacy_api/pharmacy_skus/psku-abc123" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"price": 1299,
"stock": 100,
"external_id": "YOUR-PMS-ID-001"
}'
```
See [Pharmacy SKUs](/api-reference/external-pharmacy/skus) for full documentation.
## Step 3: Register Webhooks
Set up webhooks so your system receives real-time notifications when new orders come in or stock levels change.
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/external_pharmacy_api/webhooks/" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"target": "https://your-pharmacy.com/webhooks/rxscale",
"notification_type": "pharmacy_order_created"
}'
```
Store the `webhook_secret` returned in the response. You will need it to verify webhook signatures. It is only shown once.
We recommend subscribing to these event types:
| Event Type | Why |
| ---------------------------- | -------------------------------------------------------------------------------------- |
| `pharmacy_order_created` | Get notified immediately when a new order is assigned to your pharmacy |
| `pharmacy_order_updated` | Track status changes (e.g. cancellations) and shipments the pharmacy adds to the order |
| `pharmacy_sku_stock_updated` | Stay in sync if stock is adjusted externally |
See [Webhook Security](/webhooks/security) for how to verify webhook signatures.
## Step 4: Process Orders
When you receive a `pharmacy_order_created` webhook (or poll the orders endpoint), process the order through your pharmacy workflow:
### 4a. View Order Details
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/external_pharmacy_api/pharmacy_orders/po-abc123" \
-H "X-API-Key: your-api-key-here"
```
This returns the full order including patient data, doctor data, prescription files, and line items. `doctor_data` and `prescription_file` are `null` for over-the-counter (OTC) orders that have no prescription attached.
### 4b. Update Order Status
As you process the order, update its status:
```bash theme={null}
curl -X PATCH "https://api.rxscale.com/v1/external_pharmacy_api/pharmacy_orders/po-abc123/status" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{"status": "in-progress"}'
```
And when the order is packed and ready for pickup or shipping:
```bash theme={null}
curl -X PATCH "https://api.rxscale.com/v1/external_pharmacy_api/pharmacy_orders/po-abc123/status" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{"status": "ready_for_pickup"}'
```
Complete the order through the dedicated completion endpoint so RxScale can reduce stock and publish shipment and order update events:
```bash theme={null}
curl -X PATCH "https://api.rxscale.com/v1/external_pharmacy_api/pharmacy_orders/po-abc123/complete_order" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"tracking_links": [
{
"tracking_link": "https://tracking.example.com/parcel/123",
"carrier": "DHL"
}
]
}'
```
See [Order Lifecycle](/guides/order-lifecycle) for the full status flow.
## Integration Checklist
* Received API key from RxScale
* Verified connectivity with health check endpoint
* Tested on development environment before production
* Fetched full SKU catalog
* Mapped external IDs to your pharmacy management system
* Set initial prices and stock levels
* Scheduled regular stock sync (or use webhooks)
* Registered webhooks for `pharmacy_order_created` and `pharmacy_order_updated`
* Stored signing secrets securely
* Implemented signature verification
* Endpoint responds with `2xx` within 5 seconds
* Handling incoming orders (via webhook or polling)
* Updating order status as you process and ship orders
* Completing orders through the dedicated `complete_order` endpoint
* Handling edge cases (cancellations, stock issues)
# Questionnaire Integration
Source: https://docs.rxscale.com/guides/questionnaire-integration
Embed RxScale questionnaires on your storefront and handle submissions
# Questionnaire Integration
RxScale provides embeddable medical questionnaires that collect patient information for doctor consultations. These questionnaires can be created and customized through the RxScale Admin Tool.
## Installation
On the questionnaire detail page in the Admin Tool, you'll find an installation script. Questionnaire releases are versioned, allowing you to test new versions safely.
Add the following to your page:
```html theme={null}
```
Replace `YOUR_QUESTIONNAIRE_UID` with the UID from your Admin Tool questionnaire detail page.
RxScale automatically renders the questionnaire and responds to its configured type:
* **Product Recommender** — Displays a recommendation flow based on patient answers
* **Direct To Cart** — Redirects users to the cart after completion (unless otherwise configured)
## Event Hooks
RxScale supports optional JavaScript hooks that trigger on specific events. These hooks are independent and modular.
### Window Event Handler
After a successful questionnaire submission, RxScale invokes a callback named `rxscaleQuestionnaireCompleted`:
```javascript theme={null}
window.rxscaleQuestionnaireCompleted = ({ submissionId }) => {
// Custom logic, e.g., redirect or analytics
};
```
### DataLayer (Google Tag Manager)
RxScale pushes structured events into the `dataLayer` object for Google Tag Manager integration. Events are triggered on page transitions and upon questionnaire completion.
Available variables:
* `nextStep` — Next step identifier
* `previousStep` — Previous step identifier
* `stepName` — Current step name
* `questionnaireVersion` — Questionnaire version
* `questionnaireName` — Questionnaire name
* `questionnaireId` — Questionnaire UID
### Klaviyo Integration
The Klaviyo snippet must be installed on your webpage for this integration to function.
When a user answers a questionnaire, their response data can identify a Klaviyo profile or trigger an event via the existing Klaviyo SDK on your site.
### Helium Integration
The Helium snippet must be installed on your webpage for this integration to function.
User responses can identify or create a Helium account, enabling features like pre-filling login fields based on previously submitted answers.
## Attaching Submissions to Orders
To associate a questionnaire submission with a Shopify order, include identifying metadata in the order. The required information can be stored at the order level, on an order item, or — for bundles — on that item's Shopify line item group.
### Required Properties
| Property | Required For | Description |
| --------------------------------- | ---------------------- | --------------------------------- |
| `_anamnesis_uid` | RxScale questionnaires | Links the submission to the order |
| `_external_submission_identifier` | External providers | Your external submission ID |
| `_external_provider_uid` | External providers | Your external provider UID |
Property names must match **exactly**. RxScale compares them character-for-character and is case-sensitive, so `_anamnesis_uid` (and every other key above) must be spelled precisely as shown — no typos, no altered casing, no leading/trailing whitespace, and no missing or extra underscore. If a name does not match exactly, RxScale silently ignores it and the submission is **not** linked to the order.
RxScale resolves these properties with a three-level fallback: the **line item's own property** wins first, if present. If the item doesn't have it, RxScale checks the property on the item's **Shopify line item group** — the grouping Shopify uses for bundles, where several line items share one group and a group-level property applies to all of them. If neither is set, RxScale falls back to the **order-level property**.
### Example: RxScale Questionnaire
```javascript theme={null}
fetch('/cart/update.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
attributes: {
_anamnesis_uid: "SUBMISSION_UID"
}
})
});
```
### Example: External Questionnaire Provider
```javascript theme={null}
fetch('/cart/update.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
attributes: {
_external_submission_identifier: "YOUR_EXTERNAL_ID",
_external_provider_uid: "YOUR_PROVIDER_UID"
}
})
});
```
Storing `_anamnesis_uid` at the order level allows Shopify to duplicate orders if needed (e.g., via reorder functionality).
## Pharmacy Selection
To associate a selected pharmacy with a submission, send the pharmacy information to the RxScale API:
```javascript theme={null}
fetch(`https://api.rxscale.com/api/v3-1/anamnesis/${anamnesis_uid}/attributes/pharmacy`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
key: "pharmacy_email",
value: pharmacyEmail, // empty string if pharmacy uses API integration
pharmacy_uid: pharmacyUid
})
});
```
If the selected pharmacy receives data through an API integration (no email), pass an empty string for the `value` field.
## Patient Status Check
After Shopify login, check when a patient last signed an anamnesis using their customer ID:
```javascript theme={null}
const response = await fetch(
"https://api.rxscale.com/api/v0/patient/intent" +
"?shop_customer_id=CUSTOMER_ID" +
"&shop_identifier=YOUR_SHOP" +
"&intent=INTENT_NAME"
);
const data = await response.json();
console.log(data.return_code);
```
The questionnaire must be tagged with the intent in the Admin Tool for this to work.
### Return Codes
| Code | Meaning |
| ----- | ---------------------------------------------------------------------- |
| `100` | No previous submission, or last signed submission older than 24 months |
| `200` | Last signed submission between 12 and 24 months ago |
| `300` | Last signed submission within the last 12 months |
Example response:
```json theme={null}
{
"return_code": 300
}
```
## Acquisition funnel reporting
The Admin Tool reports a questionnaire **acquisition funnel** — submitted → order placed → order completed — measuring how anonymous questionnaire submissions convert into orders. A submission is linked to an order via the order item it produced.
Questionnaire submissions that are already attached to a patient are **excluded** from the funnel. They represent existing patients rather than new acquisition, so counting them would distort the conversion rates. Only anonymous acquisition submissions are counted, and the funnel is reported at the organisation level (acquisition submissions are not bound to a single shop until an order is placed).
# Delivery Types
Source: https://docs.rxscale.com/help-center/delivery-types
Learn about the different delivery options available for orders
# Delivery Types
RxScale supports several delivery methods for getting medication to patients. The delivery type is selected when an order is placed and affects how the order is processed and fulfilled. It can also be derived automatically from the connected shop's fulfillment method, which a store owner maps to a delivery type per shop. When no delivery type can be determined, an order may have no delivery type set.
## Available Delivery Types
### Standard Shipping
The most common delivery option. The pharmacy packs the order and ships it to the patient's address using a standard postal or courier service.
* **Typical delivery time:** A few business days, depending on the shipping provider and destination.
* **Best for:** Non-urgent medication refills and routine orders.
### Express Shipping
A faster shipping option for patients who need their medication sooner. The pharmacy prioritizes the order and uses an express courier service.
* **Typical delivery time:** Next business day or same day, depending on availability and location.
* **Best for:** Urgent medication needs where the patient cannot wait for standard shipping.
### Pharmacy Pickup
The patient picks up the order directly at the pharmacy. No shipping is involved.
* **Typical delivery time:** Available as soon as the pharmacy has prepared the order.
* **Best for:** Patients who live near the pharmacy or need their medication immediately.
## How Delivery Type Affects Order Processing
The delivery type influences several aspects of how an order moves through the system:
| Aspect | Standard Shipping | Express Shipping | Pharmacy Pickup |
| ------------------- | -------------------------------------- | -------------------------------------------------- | ------------------------------- |
| Pharmacy selection | May be routed to any suitable pharmacy | Routed to pharmacies that support express delivery | Routed to the selected pharmacy |
| Processing priority | Normal | Higher priority | Normal |
| Packaging | Standard shipping packaging | Express shipping packaging | Counter pickup packaging |
| Tracking | Shipping tracking may be provided | Shipping tracking is typically provided | Not applicable |
## Frequently Asked Questions
In most cases, the delivery type cannot be changed after the order has been placed. Contact the support team if you need to make a change.
Yes, different delivery types may have different costs. Express shipping is typically more expensive than standard shipping. Pharmacy pickup usually has no shipping cost.
The system automatically routes express orders to pharmacies that support express delivery. You do not need to select a pharmacy manually for express orders.
## Related Topics
* [Order Statuses](/help-center/order-statuses) -- Track where your order is in the process.
* [Pharmacy Order Statuses](/help-center/pharmacy-order-statuses) -- See what happens at the pharmacy.
* [Fulfillment Statuses](/help-center/fulfillment-statuses) -- Track fulfillment progress.
# Fulfillment Statuses
Source: https://docs.rxscale.com/help-center/fulfillment-statuses
Track fulfillment progress from open to finished
# Fulfillment Statuses
Fulfillment orders track the physical preparation and delivery of medication. Each fulfillment order has a status that shows where it is in the process.
## Status Overview
| Status | What It Means |
| ------------ | --------------------------------------------------------------------------------------------------------- |
| `OPEN` | The fulfillment order has been created and is ready for processing. No one has started working on it yet. |
| `PROCESSING` | The order is currently being fulfilled. Items are being picked, packed, or prepared for shipment. |
| `CANCELLED` | The fulfillment was cancelled. The order will not be shipped. |
| `FINISHED` | Fulfillment is complete. The order has been packed, shipped, or handed off for delivery. |
## Fulfillment Flow
```
OPEN
|
v
PROCESSING -----> CANCELLED
|
v
FINISHED
```
### Step by Step
1. **OPEN** -- A new fulfillment order is created when an order is ready to be physically prepared and has been assigned to a pharmacy. For prescription orders, this happens after the prescription has been signed. Orders containing only over-the-counter (OTC) products reach this stage without a prescription-signing step.
2. **PROCESSING** -- The pharmacy has started working on the fulfillment. This means items are being gathered and the package is being prepared.
3. **FINISHED** -- Everything is done. The medication has been packed and dispatched. The fulfillment process is complete.
4. **CANCELLED** -- If the fulfillment needs to be stopped (for example, because the order was cancelled or reassigned), it moves to this status. A fulfillment can be cancelled from either the OPEN or PROCESSING state.
## Related Topics
* [Pharmacy Order Statuses](/help-center/pharmacy-order-statuses) -- See what each status means from the pharmacy's perspective.
* [Order Statuses](/help-center/order-statuses) -- Understand overall order statuses.
* [Delivery Types](/help-center/delivery-types) -- Learn about shipping and delivery options.
# Notifications
Source: https://docs.rxscale.com/help-center/notifications
Understand what notifications are available and how to subscribe to them
# Notifications
RxScale sends notifications to keep pharmacies, doctors, and admins informed about important events. This page explains what types of notifications are available, what triggers them, and who receives them.
## Notification Types
### Pharmacy Order Notifications
These notifications are sent when something happens with a pharmacy order.
| Event | What Triggers It | Who Receives It |
| ---------------------- | --------------------------------------------------------- | --------------- |
| Pharmacy order created | A new order is assigned to the pharmacy. | Pharmacy staff |
| Pharmacy order updated | An existing order's status, details, or shipments change. | Pharmacy staff |
**Why it matters:** These notifications help pharmacies stay on top of incoming orders and status changes, so they can process orders promptly.
### Stock Level Change Notifications
| Event | What Triggers It | Who Receives It |
| ------------------- | ---------------------------------------------------------------------------------------------- | ---------------------- |
| Stock level changed | A product's stock level at the pharmacy is updated (for example, after an order is fulfilled). | Pharmacy staff, admins |
**Why it matters:** Keeping track of stock levels helps pharmacies avoid running out of medication and ensures orders can be fulfilled without delays.
### Prescription Notifications
| Event | What Triggers It | Who Receives It |
| -------------------- | -------------------------------------------------------------------------- | --------------- |
| Prescription created | A new prescription is created in the system. | Doctors, admins |
| Prescription updated | A prescription's status changes (for example, approved, signed, declined). | Doctors, admins |
| Prescription printed | A prescription is printed (for example, for records or delivery). | Admins |
**Why it matters:** Doctors need to know when new prescriptions are waiting for their review, and admins need visibility into the prescription lifecycle.
### Order Notifications
| Event | What Triggers It | Who Receives It |
| ------------- | ------------------------------------ | --------------- |
| Order created | A new order is placed. | Admins |
| Order updated | An order's status or details change. | Admins |
**Why it matters:** Admins can monitor the overall order flow and intervene quickly if an order gets stuck or needs attention.
### Secure Chat Notifications
| Event | What Triggers It | Who Receives It |
| ---------------- | -------------------------------------------------------- | --------------------------------------- |
| New chat message | A patient sends a message in a secure chat conversation. | The doctor assigned to the conversation |
Doctors receive an email and, if a phone number is configured, an SMS alert when a patient sends them a new secure chat message. The alert contains no message content — only a reference to the conversation so the doctor can open it directly.
**Why it matters:** Doctors are promptly notified of new patient messages without any sensitive content being transmitted in the notification itself.
### Appointment Update Notifications
| Event | What Triggers It | Who Receives It |
| ----------------------- | ----------------------------------------------- | ----------------------------------------------------------------------- |
| Appointment cancelled | A scheduled appointment is cancelled. | Patient, doctor, and/or admins -- whichever the organisation subscribed |
| Appointment rescheduled | A scheduled appointment is moved to a new time. | Patient, doctor, and/or admins -- whichever the organisation subscribed |
| Appointment confirmed | A scheduled appointment is confirmed. | Patient, doctor, and/or admins -- whichever the organisation subscribed |
These three are delivered by **email** rather than by webhook, and they are **off by default**. An admin enables them per recipient from Settings -> Notifications in the admin portal, choosing whether cancellations, reschedules, confirmations, or any combination send mail. Rescheduling sends a single email about the new time, never a cancellation followed by a booking; confirmation is independent of that and fires whenever a fresh booking is confirmed, whether or not cancellation or rescheduling are also enabled. Subscribing admins sends the email to every user in the organisation, not a single recipient. Doctors can switch off their own copy from the Doctor Portal without affecting the emails patients and admins receive. No other appointment change sends email.
**Why it matters:** Patients, doctors, and your team hear about a change to an appointment straight away, without anyone having to write to them by hand.
## How to Subscribe to Notifications
Most notifications are delivered via **webhooks**, but a few are sent by RxScale itself -- the appointment update emails above, and the secure chat alerts to doctors. A webhook is a URL on your system that RxScale calls whenever an event occurs. To receive webhook notifications:
1. **Set up a webhook endpoint.** Create a URL on your system that can receive HTTP POST requests from RxScale.
2. **Register the webhook.** Use the RxScale admin panel or API to register your webhook URL and select the events you want to receive.
3. **Start receiving notifications.** Whenever a subscribed event occurs, RxScale will send a POST request to your webhook URL with the event details.
Webhook setup requires technical configuration. If you need help, work with your development team or contact RxScale support.
## What a Notification Looks Like
When an event occurs, RxScale sends a JSON payload to your webhook URL. The payload includes:
* **Event type** -- What happened (for example, `pharmacy_order_created`).
* **Timestamp** -- When the event occurred.
* **Data** -- The relevant details about the order, prescription, or other object involved.
## Tips for Managing Notifications
* **Subscribe only to events you need.** This keeps your system focused and avoids unnecessary processing.
* **Monitor your webhook endpoint.** Make sure your endpoint is available and responding. RxScale may retry failed deliveries, but persistent failures could cause you to miss notifications.
* **Process notifications quickly.** Acknowledge the webhook request promptly to avoid timeouts. Handle any heavy processing asynchronously.
## Frequently Asked Questions
Most real-time notifications are delivered via webhooks, but some are sent by RxScale directly. Appointment cancellation, rescheduling, and confirmation emails are one of those: an admin sets them up from Settings -> Notifications in the admin portal, and enabling them does not change what your webhooks receive. Secure chat alerts to doctors are another. For anything else, contact the support team.
RxScale will retry the delivery. If the endpoint remains unavailable, the notification may be lost. It is important to keep your webhook endpoint running and monitored.
Yes, you can use the development environment to test webhook notifications. See the API documentation for development base URLs.
## Related Topics
* [Webhooks Overview](/webhooks/overview) -- Technical details on how webhooks work.
* [Webhook Events](/webhooks/events) -- Full list of webhook event types and payload formats.
* [Webhook Security](/webhooks/security) -- How to verify webhook signatures.
# Order Statuses
Source: https://docs.rxscale.com/help-center/order-statuses
Understand what each order status means and how orders move through the system
# Order Statuses
Every order in RxScale goes through a series of statuses as it moves from creation to completion. This page explains what each status means in plain language.
## Status Overview
| Status | Display Name | Description |
| -------------------------- | ---------------------- | ------------------------------------------------------ |
| `init` | Order created | The order has just been placed. |
| `started` | Processing started | The system is processing your order. |
| `waiting for doctor` | Awaiting doctor review | A doctor needs to review and approve the prescription. |
| `waiting for pharmacy` | Sent to pharmacy | The order has been sent to a pharmacy for fulfillment. |
| `paused` | Paused | The order is temporarily on hold. |
| `completed` | Completed | The order has been successfully fulfilled. |
| `cancelled` | Cancelled | The order was cancelled. |
| `out of stock` | Out of stock | One or more items are currently unavailable. |
| `waiting for manual input` | Requires attention | The order needs manual review by the support team. |
| `waiting for clearance` | Awaiting clearance | The order is waiting for authorization. |
| `waiting for payment` | Payment pending | Waiting for payment confirmation. |
| `pharmacy not found` | No pharmacy available | No suitable pharmacy was found for this order. |
| `prescription declined` | Prescription declined | The doctor declined the prescription. |
| `prescription not found` | Prescription missing | The prescription could not be located. |
| `prescription not signed` | Awaiting signature | The prescription has not been signed yet. |
## Happy Path: How a Typical Order Flows
The diagram below shows the normal path an order takes when everything goes smoothly.
```
init
|
v
started
|
v
waiting for doctor
|
v
waiting for pharmacy
|
v
completed
```
A patient places an order. The system creates the order and begins initial processing.
The system validates the order details and prepares it for doctor review.
The order is sent to a doctor for review. The doctor will check the prescription request, verify it is appropriate, and either approve or decline it.
After the doctor approves and signs the prescription, the order is routed to a pharmacy. The pharmacy will prepare the medication.
The pharmacy has fulfilled the order and the patient has received their medication. The order is now closed.
## Other Statuses Explained
### Paused
An order may be paused if additional information is needed or if there is a temporary issue that prevents the order from moving forward. Once the issue is resolved, the order will resume.
### Out of Stock
This means one or more products in the order are currently not available at the assigned pharmacy. The system may try to find an alternative pharmacy, or the order may need to wait until stock is replenished.
### Requires Attention (waiting for manual input)
The order has encountered a situation that cannot be resolved automatically. A member of the support team will review the order and take the appropriate action.
### Awaiting Clearance (waiting for clearance)
The order is waiting for an authorization step to be completed before it can proceed.
### Payment Pending (waiting for payment)
The patient's payment has not yet been confirmed. The order will proceed once payment is received.
### No Pharmacy Available (pharmacy not found)
The system could not find a pharmacy that can fulfill this order. This may happen if the required products are not stocked by any connected pharmacy. The support team will work to resolve this.
### Prescription Declined
The doctor reviewed the prescription and decided not to approve it. The patient may need to schedule a new consultation or provide additional information.
### Prescription Missing (prescription not found)
The prescription associated with this order could not be located in the system. The support team will investigate.
### Awaiting Signature (prescription not signed)
The doctor has approved the prescription, but it has not yet been electronically signed. The prescription must be signed before the order can be sent to a pharmacy. See [Prescription Signing](/help-center/signing) for more details.
### Cancelled
The order was cancelled. This can happen for various reasons, such as a patient request, a doctor decision, or a system-level cancellation. Cancelled orders cannot be resumed.
# Help Center
Source: https://docs.rxscale.com/help-center/overview
Learn how RxScale works — from orders to prescriptions to pharmacy fulfillment
# Help Center
This help center explains how RxScale works — from orders to prescriptions to pharmacy fulfillment. Whether you are a pharmacist, doctor, or admin, you will find clear explanations of statuses, processes, and features here.
## What is RxScale?
RxScale is a digital platform that connects patients, doctors, and pharmacies to manage prescriptions and medication orders. It handles the entire workflow: a patient places an order, a doctor reviews and signs the prescription when one is required, and a pharmacy fulfills it. Orders that contain only over-the-counter (OTC) products go directly to a pharmacy without a doctor review step.
## Topics
Understand what each order status means and how orders move through the system.
Learn about prescription review, approval, and signing statuses.
See what each status means from the pharmacy's perspective and what actions to take.
Track fulfillment progress from open to finished.
Understand how electronic prescription signing (QES) works.
Learn about the different delivery options available for orders.
Find out how digital wallet passes work for patient identity verification.
See what notifications are available and how to subscribe to them.
## Need Help?
If you cannot find the answer you are looking for, contact your RxScale account manager or reach out to our support team.
# Pharmacy Order Statuses
Source: https://docs.rxscale.com/help-center/pharmacy-order-statuses
Understand order statuses from the pharmacy's perspective and what actions to take
# Pharmacy Order Statuses
When a pharmacy receives an order through RxScale, the order goes through a series of statuses. This page explains what each status means from the pharmacy's perspective and what you should do at each stage.
## Status Overview
| Status | What It Means |
| ---------------------- | ------------------------------------------------------------------------------ |
| `init` | Order received but not yet reviewed. The order has just arrived in the system. |
| `waiting for pharmacy` | Order is in your queue, waiting for you to process it. |
| `pending review` | You are reviewing the order and checking availability. |
| `on-hold` | Processing is paused while the pharmacy and admin team clarify an issue. |
| `in-progress` | You are currently preparing the order (picking, packing medication). |
| `ready_for_pickup` | The order is packed and ready for pickup or shipping. |
| `completed` | The order has been fulfilled and delivered to the patient. |
| `cancelled` | The order was cancelled and no longer needs to be processed. |
## Order Flow
```
init
|
v
waiting for pharmacy
|
v
pending review
|
v
on-hold
|
v
in-progress
|
v
ready_for_pickup
|
v
completed
```
## What Should I Do?
**No action needed yet.** The order has just been created and is being set up in the system. It will move to your queue shortly.
**Review the order.** Open the order to see what products are requested and check if you have them in stock. Move the order to "pending review" when you start looking at it.
**Verify stock and prescription details.** Confirm that you have the requested medication in stock and, when a prescription is attached, that the prescription details are correct. Orders containing only over-the-counter (OTC) products have no prescription to verify. If everything looks good, begin preparing the order.
**Pause processing and add context.** Use this status when the order needs clarification from the admin team before you can continue. Add a comment that explains what needs to be checked. When you move the order back to `in-progress`, the related clarification issue is resolved automatically.
**Pick and pack the medication.** Gather the requested items and prepare the package for shipping or pickup. Once the order is ready, update the status.
**Hand off the order.** The package is ready. If it is being shipped, hand it to the shipping carrier. If the patient is picking it up, have it ready at the counter. Confirm this status only when the order is actually ready — patients are notified as soon as you mark it ready for pickup.
**No further action needed.** The order has been delivered and is now closed. Keep your records updated for reference. Completing an order also notifies the patient that it has been fulfilled.
**Stop processing if you have not shipped yet.** If you have already begun preparing the order, return the items to stock. No shipment should be made for cancelled orders.
## Tips for Pharmacies
* **Process orders promptly.** Moving orders through statuses quickly keeps patients informed and improves the overall experience.
* **Keep statuses up to date.** Accurate status tracking helps patients know exactly where their order stands.
* **Confirm before notifying.** The pharmacy app asks you to confirm before moving an order to `ready_for_pickup` or `completed`, because those statuses notify the patient.
* **Check for cancellations.** Before shipping an order, verify that it has not been cancelled in the meantime.
* **Contact support if something is wrong.** If an order has incorrect details or you cannot fulfill it, reach out to the RxScale support team rather than ignoring the order.
## Related Topics
* [Order Statuses](/help-center/order-statuses) -- See how order statuses work from the overall system perspective.
* [Fulfillment Statuses](/help-center/fulfillment-statuses) -- Learn about fulfillment tracking.
* [Notifications](/help-center/notifications) -- Set up notifications so you never miss a new order.
# Prescription Statuses
Source: https://docs.rxscale.com/help-center/prescription-statuses
Learn about prescription review, approval, and signing statuses
# Prescription Statuses
Every prescription in RxScale goes through a review and signing process before it becomes legally valid. This page explains what each prescription status means.
## Status Overview
| Status | What It Means |
| -------------------- | ------------------------------------------------------------------------------------------------------------- |
| `waiting for doctor` | A doctor has not yet reviewed this prescription. It is in the queue waiting for a doctor to take action. |
| `approved` | The doctor approved the prescription. It will be electronically signed next. |
| `signed` | The prescription has been electronically signed (QES) and is legally valid. It can now be sent to a pharmacy. |
| `declined` | The doctor declined this prescription. The patient may need a new consultation. |
| `cancelled` | The prescription was cancelled. This can happen at the request of the patient, the doctor, or the system. |
| `ON_HOLD` | The prescription is on hold for further review by the support team. |
| `NON_QES_SIGNED` | The prescription was signed without a qualified electronic signature. |
## How a Prescription Moves Through the System
```
waiting for doctor
|
---------
| |
v v
approved declined
|
v
signed
```
### Step by Step
1. **Waiting for doctor** -- When a prescription is created, it starts here. A doctor needs to review the patient's information and the requested medication.
2. **Approved** -- The doctor has reviewed the prescription and confirmed it is appropriate. The prescription is now queued for electronic signing.
3. **Signed** -- The prescription has been electronically signed using a qualified electronic signature (QES). This makes it legally valid. The associated order can now move forward to a pharmacy.
4. **Declined** -- If the doctor determines the prescription is not appropriate, they will decline it. The patient will be notified and may need to schedule a new consultation.
5. **Cancelled** -- A prescription can be cancelled at various stages if it is no longer needed.
## Special Statuses
### ON\_HOLD
A prescription placed on hold requires additional review by the support team. This may happen if there are questions about the patient's information, the medication, or other factors that need clarification before the prescription can proceed.
### NON\_QES\_SIGNED
In some cases, a prescription may be signed without a qualified electronic signature. This status indicates the prescription has been signed, but not with the full legal QES standard. This is used in specific situations where QES is not required or not yet available.
## Related Topics
* [Prescription Signing](/help-center/signing) -- Learn how the electronic signing process works.
* [Order Statuses](/help-center/order-statuses) -- See how prescription statuses affect the overall order.
# Prescription Signing
Source: https://docs.rxscale.com/help-center/signing
Understand how electronic prescription signing (QES) works in RxScale
# Prescription Signing
Before a prescription can be sent to a pharmacy, it must be electronically signed by the prescribing doctor. This page explains how the signing process works.
## What is Electronic Signing (QES)?
QES stands for **Qualified Electronic Signature**. It is a legally binding digital signature that has the same legal validity as a handwritten signature. In Germany and the EU, QES is required for electronic prescriptions to be legally valid.
When a doctor signs a prescription with QES:
* The prescription becomes a legally valid document.
* The patient can use it at any pharmacy.
* The signing is traceable and tamper-proof.
## How the Signing Process Works
```
Doctor approves prescription
|
v
Signing request created (IN_PROGRESS)
|
v
Doctor signs via sign center (RxScaleSign)
|
v
Signing request completed (DONE)
|
v
Prescription status changes to "signed"
```
### Step by Step
1. **Doctor approves the prescription.** After reviewing the patient's information and medication request, the doctor approves the prescription in the RxScale system.
2. **A signing request is created.** The system creates a signing request and sends it to the sign center. The signing request status is set to `IN_PROGRESS`.
3. **The doctor signs via the sign center.** RxScale uses RxScaleSign as the signing provider. Doctors can sign prescriptions individually or in batches through the sign center. Batch signing allows doctors to sign multiple prescriptions at once, saving time.
4. **The signing request is completed.** Once the doctor has applied their qualified electronic signature, the signing request status changes to `DONE`.
5. **The prescription status updates.** The prescription moves from "approved" to "signed". It is now legally valid and the associated order can proceed to a pharmacy.
## Signing Request Statuses
| Status | What It Means |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `IN_PROGRESS` | The signing request has been created and is waiting for the doctor to sign. The doctor may see this in their sign center queue. |
| `DONE` | The doctor has successfully signed the prescription. The prescription is now legally valid. |
| `EXPIRED` | The signing request expired before the doctor signed it. This can happen if the doctor does not sign within the allowed time window. A new signing request may need to be created. |
## The Sign Center
The sign center is where doctors go to review and sign pending prescriptions. Key features:
* **Batch signing** -- Doctors can select multiple prescriptions and sign them all at once, rather than signing each one individually. This is especially useful when there are many prescriptions waiting.
* **Powered by RxScaleSign** -- The sign center uses RxScaleSign, a certified provider of qualified electronic signatures, to ensure legal compliance.
* **Secure authentication** -- Doctors must authenticate themselves before signing to ensure only authorized doctors can sign prescriptions.
### Optional Batch Review Before RxScaleSign
Doctors can enable an additional review step before starting a batch with RxScaleSign. When enabled, the Sign Center shows every prescription that will be included in the next batch together with the associated questionnaire information.
The doctor can remove individual prescriptions before signing. Each removed prescription requires its own comment and is returned for follow-up review. After the doctor confirms the batch, the remaining prescriptions continue to RxScaleSign.
## What Happens After Signing?
Once a prescription is signed:
1. The prescription status changes to **signed**.
2. The associated order moves to **waiting for pharmacy** status.
3. The order is routed to an appropriate pharmacy for fulfillment.
4. The patient and pharmacy are notified that the prescription is ready.
## Frequently Asked Questions
If a signing request expires, the system may automatically create a new one, or the support team may need to intervene. The prescription remains in "approved" status until it is successfully signed.
In most cases, no. A qualified electronic signature is required for legal validity. In rare situations, a prescription may be marked as NON\_QES\_SIGNED, but this is only used in specific circumstances.
Signing itself takes just a few seconds. The total time depends on when the doctor reviews and signs the prescription in the sign center.
## Related Topics
* [Prescription Statuses](/help-center/prescription-statuses) -- See all prescription statuses and their meanings.
* [Order Statuses](/help-center/order-statuses) -- Understand how signing affects the overall order flow.
# Wallet Passes
Source: https://docs.rxscale.com/help-center/wallet-passes
Learn how digital wallet passes work for patient identity verification
# Wallet Passes
RxScale uses digital wallet passes to help verify patient identity. This page explains what wallet passes are, how they work, and how patients use them.
## What is a Wallet Pass?
A wallet pass is a digital card that lives on the patient's phone, in their Apple Wallet or Google Wallet app. It contains a QR code that can be scanned to verify the patient's identity.
Think of it like a digital membership card: the patient always has it on their phone and can show it whenever they need to prove who they are.
## How Does It Work?
```
Patient receives wallet pass
|
v
Patient adds it to their phone wallet
|
v
At verification, patient shows QR code
|
v
Staff scans the QR code
|
v
System sends OTP to the patient
|
v
Patient provides OTP to confirm identity
```
### Step by Step
1. **Patient receives a wallet pass.** When a patient is registered in the RxScale system, they can receive a wallet pass via email or a direct link.
2. **Patient adds it to their phone.** The patient taps the link to add the pass to their Apple Wallet or Google Wallet. The pass is now stored on their phone and accessible at any time.
3. **Showing the QR code.** When the patient needs to verify their identity (for example, when picking up medication at a pharmacy), they open their wallet app and show the QR code on the pass.
4. **Scanning the QR code.** The pharmacy staff or other authorized person scans the QR code using the RxScale system.
5. **OTP verification.** After the QR code is scanned, the system sends a one-time password (OTP) to the patient's email address, and also by SMS to the phone number registered in their patient profile when one is on file. The patient tells the staff member the code they received.
6. **Identity confirmed.** Once the OTP is verified, the patient's identity is confirmed and the process can continue (for example, handing over the medication).
## Why Use a Wallet Pass?
* **Convenient** -- The patient always has it on their phone. No need to carry a physical card.
* **Secure** -- The QR code alone is not enough to verify identity. The additional OTP step ensures that only the actual patient can complete verification.
* **Fast** -- Scanning a QR code and entering an OTP takes just a few seconds.
## Frequently Asked Questions
Alternative identity verification methods are available. Contact the support team for options.
The patient should contact the support team to update their registered phone number before attempting verification.
Wallet passes may be updated or replaced over time. Patients will be notified if they need to update their pass.
While the QR code can be screenshot, the OTP verification step prevents someone else from using it. The OTP is sent to the patient's own email address and registered phone number, so only the actual patient can complete the verification.
## Related Topics
* [Order Statuses](/help-center/order-statuses) -- Track your order through the system.
* [Delivery Types](/help-center/delivery-types) -- Wallet passes are especially useful for pharmacy pickup orders.
# Introduction
Source: https://docs.rxscale.com/introduction
Welcome to the RxScale API documentation
# Welcome to RxScale Documenation
RxScale provides APIs for digital prescription management, pharmacy integration, and patient services. Our APIs enable pharmacies, healthcare providers, and partners to integrate prescription workflows into their existing systems.
## Available APIs
Manage pharmacy orders, update stock levels, and receive real-time webhook notifications.
Access organisation-level data including orders, prescriptions, products, doctors, and patients.
Query product catalogs and create prescription or treatment checkouts.
## Quick Start
1. **Get your API key** — Contact your RxScale account manager to receive your API credentials.
2. **Choose your API** — Select the API that matches your integration needs.
3. **Authenticate** — Include your API key in the `X-API-Key` header with every request.
4. **Start integrating** — Use this documentation to explore endpoints and build your integration.
## Base URL
```text theme={null}
https://api.rxscale.com
```
There is no separate staging or sandbox API. Test access is provided through dedicated test shops. All API requests — including those from test shops — go to the production API. Contact your RxScale account manager to set up a test shop.
## API Specification
**RxScale does not currently publish a machine-readable OpenAPI (Swagger) specification.** There is no `openapi.json` or `openapi.yaml` you can download to generate a client automatically.
This documentation is the authoritative contract. Build your integration from the endpoint pages in the API reference — each one documents the HTTP method, path, query parameters, request body, and a realistic JSON response example. Any interactive "try it" or sample specification you may see rendered elsewhere is not an RxScale API description and must not be used.
If a machine-readable specification is important for your integration, tell your RxScale account manager so we can prioritise it.
## AI Integration
Connect the RxScale documentation to your AI tools for instant access to our API reference.
### MCP Server (for coding tools)
Use our MCP (Model Context Protocol) server with AI coding assistants:
```bash theme={null}
claude mcp add --transport http rxscale-docs https://docs.rxscale.com/mcp
```
Verify with `claude mcp list`.
Add to `.cursor/mcp.json`:
```json theme={null}
{
"mcpServers": {
"rxscale-docs": {
"url": "https://docs.rxscale.com/mcp"
}
}
}
```
Add to `.vscode/mcp.json`:
```json theme={null}
{
"servers": {
"rxscale-docs": {
"type": "http",
"url": "https://docs.rxscale.com/mcp"
}
}
}
```
### llms.txt (for ChatGPT, Gemini, and other AI)
Our documentation is also available as `llms.txt` for AI tools that support this standard (ChatGPT, Gemini, Claude web, etc.):
| File | URL | Description |
| --------------- | ---------------------------------------- | ------------------------------------------- |
| `llms.txt` | `https://docs.rxscale.com/llms.txt` | Index of all pages with descriptions |
| `llms-full.txt` | `https://docs.rxscale.com/llms-full.txt` | Full documentation content in a single file |
Paste the `llms-full.txt` URL into ChatGPT or any AI chat to give it full context about the RxScale API.
## Need Help?
If you have questions about the API or need support with your integration, contact your RxScale account manager or reach out to our technical support team.
# Pagination
Source: https://docs.rxscale.com/pagination
How to paginate through list endpoints
# Pagination
All list endpoints in the RxScale APIs support pagination through query parameters.
## Query Parameters
| Parameter | Type | Default | Description |
| --------- | ------- | ------- | -------------------------- |
| `page` | integer | `0` | Page number (zero-indexed) |
| `limit` | integer | `50` | Number of items per page |
Pages are **zero-indexed**. The first page is `page=0`, not `page=1`.
## Response Format
Paginated endpoints return:
```json theme={null}
{
"data": [...],
"totalRegistries": 150,
"totalPages": 3
}
```
* `data` — Array of items for the current page
* `totalRegistries` — Total number of items across all pages
* `totalPages` — Total number of pages (calculated as `ceil(totalRegistries / limit)`)
## Limits
The maximum `limit` is **not uniform across the External Pharmacy API** — it depends on the endpoint family.
| API | Endpoints | Default Limit | Maximum Limit |
| --------------------- | -------------------------------------- | ------------- | ------------- |
| External Pharmacy API | `/pharmacy_orders`, `/payouts` | 50 | 200 |
| External Pharmacy API | `/pharmacy_skus`, `/pharmacy_products` | 50 | 500 |
| Management API | All list endpoints | 50 | 200 |
| Public API (Products) | Product listings | 50 | 150 |
Requesting a `limit` above the maximum will be capped to the maximum — the request still succeeds, it simply returns fewer items than you asked for. Always read `totalRegistries` and `totalPages` from the response rather than assuming your requested `limit` was applied.
## Requesting a Page Past the End
Requesting a page beyond the last one returns `200` with an **empty `data` array**. `totalRegistries` and `totalPages` still report the full collection, so you can tell how far it actually goes.
```json theme={null}
{
"data": [],
"totalRegistries": 75,
"totalPages": 3
}
```
A negative `page` is treated as `page=0`.
An empty `data` array is a reliable stop condition for a sync loop: the API never silently serves the last page again in place of the page you asked for.
## Example
Fetching the second page of 25 products:
```bash theme={null}
curl -X GET "https://api.rxscale.com/v1/management/products?page=1&limit=25" \
-H "X-API-Key: your-api-key"
```
Response:
```json theme={null}
{
"data": [
{"uid": "prod_abc", "name": "Product 26"},
{"uid": "prod_def", "name": "Product 27"}
],
"totalRegistries": 75,
"totalPages": 3
}
```
## Iterating Through All Pages
```python theme={null}
import requests
page = 0
all_items = []
while True:
response = requests.get(
"https://api.rxscale.com/v1/management/products",
headers={"X-API-Key": "your-api-key"},
params={"page": page, "limit": 50}
)
data = response.json()
if not data["data"]:
break
all_items.extend(data["data"])
page += 1
```
Use the smallest `limit` that makes sense for your use case. Smaller pages mean faster responses and less memory usage.
# Permissions
Source: https://docs.rxscale.com/permissions
Complete reference of API key permissions and their associated endpoints
# Permissions
Every RxScale API key carries a set of permissions that control which endpoints the key can access. This page provides a complete reference of all available permissions, grouped by API.
## How Permissions Work
When you create an API key, you assign it one or more permissions. Each API endpoint requires a specific permission -- if the key lacks that permission, the request returns `403 Permission Denied`.
```bash theme={null}
# A key with only `orders_read` can list orders...
curl -X GET "https://api.rxscale.com/v1/external_pharmacy_api/pharmacy_orders/" \
-H "X-API-Key: your-api-key-here"
# ...but cannot update order status (requires `orders_write`)
curl -X PATCH "https://api.rxscale.com/v1/external_pharmacy_api/pharmacy_orders/{uid}/status" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{"status": "in-progress"}'
# Returns 403: Permission denied
```
Permissions are set during API key creation. Contact your RxScale account manager to add or change permissions on an existing key.
## External Pharmacy API
The External Pharmacy API uses pharmacy-specific API keys. Permissions are simple string identifiers checked at runtime.
| Permission | Type | Endpoints |
| -------------------- | ----- | ----------------------------------------------------------------------- |
| `orders_read` | Read | `GET /pharmacy_orders/` -- List pharmacy orders |
| | | `GET /pharmacy_orders/{uid}` -- Get order details |
| `orders_write` | Write | `PATCH /pharmacy_orders/{uid}/status` -- Update order status |
| | | `PATCH /pharmacy_orders/{uid}/complete_order` -- Complete order |
| `stock_read` | Read | `GET /pharmacy_skus/` -- List pharmacy SKUs |
| `stock_write` | Write | `PATCH /pharmacy_skus/{uid}/stock` -- Update stock level |
| `pharmacy_sku_write` | Write | `PATCH /pharmacy_skus/{uid}` -- Update SKU (price, stock, external\_id) |
| | | `PATCH /pharmacy_skus/{uid}/stock` -- Update stock level |
| | | `PATCH /pharmacy_skus/{uid}/external_id` -- Update external ID |
| `webhooks_read` | Read | `GET /webhooks/` -- List webhook subscriptions |
| `webhooks_write` | Write | `POST /webhooks/` -- Register a webhook |
| | | `DELETE /webhooks/{uid}` -- Remove a webhook |
The `PATCH /pharmacy_skus/{uid}/stock` endpoint accepts either `stock_write` or `pharmacy_sku_write`. If your key has either permission, the request succeeds. All other SKU write endpoints require `pharmacy_sku_write` specifically.
## Management API
The Management API uses organisation-scoped API keys. Permissions follow a `resource:action` naming convention and are enforced via the `@require_api_key_permission` decorator.
| Permission | Type | Endpoints |
| ------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------- |
| `order:read` | Read | `GET /orders/{uid}` -- Get order details |
| `prescription:read` | Read | `GET /prescriptions/{uid}` -- Get prescription details |
| | | Unlocks prescription data within order responses (when combined with `order:read`) |
| `prescription:external_sign` | Write | `POST /prescriptions/{uid}/render` -- Trigger asynchronous PDF rendering |
| | | `POST /prescriptions/{uid}/external-sign` -- Mark a prescription as `EXTERNALLY_SIGNED` and dispatch to pharmacy |
| `product:read` | Read | `GET /products/` -- List products with connected SKU and shop data |
| `doctor:read` | Read | `GET /doctors/` -- List doctors |
| `doctor_statistics:read` | Read | `GET /doctors/{uid}/statistics` -- Get prescription statistics for a doctor |
| `patient:read` | Read | `GET /patients/?email={email}` -- Look up a patient by email |
| | | `GET /patients/{uid}` -- Get patient details |
| | | `GET /patients/{uid}/intent/{intent}` -- Get intent return code |
| `patient_profile_field:delete` | Write | `DELETE /patients/{uid}/patient-profile-fields/{field_key}` -- Delete a patient's value for one profile field |
| `waiting_room:read` | Read | `GET /waiting-room/{uid}/status` -- Get queue entry status |
| `waiting_room:write` | Write | `POST /waiting-room/register` -- Register a patient in the waiting room |
| | | `DELETE /waiting-room/{uid}` -- Cancel a queue registration |
| `review_link:read` | Read | `GET /review-links` -- List review links for the organisation |
| `review_link:write` | Write | `POST /review-links` -- Create a single-use review link for a pharmacy order |
| | | `DELETE /review-links/{uid}` -- Revoke a review link |
| `wallet_pass_template:read` | Read | `GET /wallet-passes/templates` -- List wallet pass templates |
| `wallet_pass:read` | Read | `GET /wallet-passes/` -- List wallet passes |
| `wallet_pass:verify` | Verify | `POST /wallet-passes/verify` -- Resolve a scanned pass to identity handles |
| `wallet_pass_push_notification:write` | Write | `POST /wallet-passes/push-notifications` -- Send push notifications |
| `notification_subscription:read` | Read | `GET /notification-subscriptions/` -- List webhook subscriptions |
| `notification_subscription:write` | Write | `POST /notification-subscriptions/` -- Create a webhook subscription |
| | | `DELETE /notification-subscriptions/` -- Remove a webhook subscription |
| | | `POST /notification-subscriptions/test` -- Send a test webhook |
| `listing_request:read` | Read | `GET /listing-requests` -- List listing requests |
| | | `GET /listing-requests/{uid}` -- Get a listing request |
| `listing_request:write` | Write | `POST /listing-requests/{uid}/accept` -- Accept a pending listing request |
| | | `POST /listing-requests/{uid}/decline` -- Decline a pending listing request |
| `anamnesis:connect_patient` | Write | `POST /anamnesis/patient-connections` -- Connect an anamnesis to a patient |
When an API key has `order:read` but not `prescription:read`, order responses will have their prescription data stripped. Add `prescription:read` to include full prescription details within order data.
`patient_profile_field:delete` is **not** implied by `patient:write`. A key that can create a patient or set profile field values cannot delete one unless this permission is granted separately. This mirrors the doctor-facing side of the product, where deleting a profile field is its own capability rather than a consequence of being able to write one -- destroying patient data is treated as more sensitive than writing it. Request this permission explicitly if your integration needs to delete field values.
### Notification Subscriptions
The notification subscription endpoints (`/notification-subscriptions/`) on the Management API require the granular `notification_subscription` permissions. Listing subscriptions requires `notification_subscription:read`; creating, removing, and sending test webhooks require `notification_subscription:write`.
| Endpoint | Method | Description | Permission |
| ---------------------------------- | -------- | ----------------------------- | --------------------------------- |
| `/notification-subscriptions/` | `GET` | List webhook subscriptions | `notification_subscription:read` |
| `/notification-subscriptions/` | `POST` | Create a webhook subscription | `notification_subscription:write` |
| `/notification-subscriptions/` | `DELETE` | Remove a webhook subscription | `notification_subscription:write` |
| `/notification-subscriptions/test` | `POST` | Send a test webhook | `notification_subscription:write` |
Keys created before these permissions were introduced must have `notification_subscription:read` / `notification_subscription:write` added before they can call these endpoints again. Contact your RxScale account manager to update an existing key.
## Public API
The Public API uses organisation-scoped API keys (with optional legacy `X-RxScale-Authorization` header support). It is designed for telemedicine providers to query products and create checkouts.
| Permission | Type | Endpoints |
| ------------------------------ | ----- | ------------------------------------------------------------------------------------------------- |
| `product:read` | Read | `GET /products/{shop_identifier}` -- List products for a shop |
| | | `POST /products/{shop_identifier}/live-stock` -- Check product stock availability before checkout |
| `order:read` | Read | `GET /orders/{shop_identifier}` -- Query order status by prescription UIDs |
| `create_prescription_checkout` | Write | `POST /prescriptions/{shop_identifier}` -- Create a prescription-based checkout |
| `create_treatment_checkout` | Write | `POST /treatments/{shop_identifier}` -- Create a treatment-based checkout |
The `product:read` and `order:read` permissions are shared between the Management API and the Public API. If a key has `product:read`, it can use both `GET /products/` on the Management API and `GET /products/{shop_identifier}` on the Public API (assuming the key is valid for both).
## Anamnesis API
The Anamnesis API's read endpoints and the standard submission endpoint are public and require no API key. The **external** submission endpoint is organisation-scoped and requires an API key with the permission below. The `provider_identifier` in the request must reference an external anamnesis provider that belongs to your organisation.
| Permission | Type | Endpoints |
| --------------------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `anamnesis:external_submit` | Write | `POST /questionnaires/{questionnaire_id}/external/submissions` -- Submit an anamnesis on behalf of an external provider owned by your organisation |
## Choosing the Right Permissions
Follow the **principle of least privilege** -- only grant the permissions your integration actually needs.
### Common Scenarios
| Integration Use Case | Recommended Permissions |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| Pharmacy order management system | `orders_read`, `orders_write` |
| Pharmacy stock sync | `stock_read`, `pharmacy_sku_write` |
| Pharmacy order + stock management | `orders_read`, `orders_write`, `stock_read`, `pharmacy_sku_write` |
| Pharmacy with webhook notifications | Add `webhooks_read`, `webhooks_write` to any of the above |
| Telemedicine provider checkout flow | `product:read`, `create_prescription_checkout` |
| Telemedicine provider with order tracking | `product:read`, `create_prescription_checkout`, `order:read` |
| Organisation analytics dashboard | `order:read`, `prescription:read`, `doctor:read`, `doctor_statistics:read`, `patient:read` |
| Waiting room integration | `waiting_room:read`, `waiting_room:write`, `patient:read` |
| Pharmacist review links | `review_link:read`, `review_link:write` |
| Wallet pass management | `wallet_pass_template:read`, `wallet_pass:read`, `wallet_pass_push_notification:write` |
| Organisation webhook notifications (Management API) | `notification_subscription:read`, `notification_subscription:write` |
### Tips
* **Separate read and write** -- If your integration only needs to display data, request only read permissions.
* **Use dedicated keys** -- Create separate API keys for different systems or environments rather than sharing a single key with all permissions.
* **Audit regularly** -- Review your API keys periodically and revoke any that are no longer in use.
* **External Pharmacy API vs. Management API** -- Pharmacy-specific integrations should use the External Pharmacy API with pharmacy API keys. Organisation-wide integrations should use the Management API with management API keys.
# Rate Limits
Source: https://docs.rxscale.com/rate-limits
API rate limiting and best practices
# Rate Limits
All RxScale APIs enforce rate limits to ensure fair usage and system stability. The limits are **per second** — there is no separate per-minute quota.
## External Pharmacy API
| Property | Value |
| ---------- | -------------------------- |
| Limit | **50 requests per second** |
| Counted by | Client IP address |
| Scope | Per API server instance |
The limit is keyed on the **client IP address**, not on your API key. If several systems share one outbound IP (for example behind a single NAT gateway), they share the same 50 requests-per-second budget. Conversely, requests from different outbound IPs are counted separately.
Rate limit counters are held in memory on each API server instance, and the API runs on multiple auto-scaled instances. The effective throughput you observe can therefore be somewhat higher than 50 requests per second, because your requests may be spread across instances. Treat **50 requests per second per IP** as the guaranteed budget you should design against — do not rely on the extra headroom, as it varies with how traffic is distributed.
## Other APIs
| API | Limit |
| --------------------- | ---------------------- |
| External Pharmacy API | 50 requests per second |
| Management API | 10 requests per second |
| Public API | 10 requests per second |
Limits are configured per API and may change. If your integration needs a higher limit, contact your RxScale account manager.
## Rate Limit Response
When you exceed the rate limit, the API returns:
```json theme={null}
{
"error": "Rate limit exceeded"
}
```
**HTTP Status:** `429 Too Many Requests`
## Request Size Limit
Every RxScale API also caps the size of a single request body.
| Limit | Value |
| -------------------- | -------------------- |
| Maximum request body | 32 MiB (about 33 MB) |
A larger request is rejected before it is processed:
**HTTP Status:** `413 Content Too Large`
JSON requests are far below this limit in normal use. If you are sending a very
large batch, split it across several requests rather than growing a single one —
see **Batch operations** below.
## Best Practices
* **Use webhooks** instead of polling for real-time updates. Register webhook subscriptions to receive notifications when orders or stock levels change.
* **Cache responses** where appropriate. Product catalogs and SKU lists change infrequently.
* **Implement exponential backoff** when you receive `429` responses. Wait, then retry with increasing intervals.
* **Batch operations** when possible rather than making individual requests for each item.
# Events
Source: https://docs.rxscale.com/webhooks/events
Webhook event types and payload structures
# Webhook Events
RxScale sends webhooks for the following event types:
| Event Type | Description |
| -------------------------------- | -------------------------------------------------------------------------------------------------- |
| `pharmacy_order_created` | A new pharmacy order was created and assigned to your pharmacy |
| `pharmacy_order_updated` | An existing pharmacy order changed — including status changes and when the pharmacy adds shipments |
| `pharmacy_sku_stock_updated` | A pharmacy SKU's stock level changed |
| `appointment_reminder_due` | A scheduled appointment reminder became due |
| `patient_doctor_meeting_updated` | A patient-doctor meeting changed lifecycle state |
## HTTP Headers
Every webhook request includes the following HTTP headers:
| Header | Description |
| --------------------- | ------------------------------------------------------------------------------ |
| `Content-Type` | Always `application/json` |
| `X-Webhook-Event` | The event type (e.g. `pharmacy_order_created`) |
| `X-Webhook-Signature` | HMAC-SHA256 signature of the request body (see [Security](/webhooks/security)) |
If you configured a custom header when creating the subscription, it will also be included.
***
## pharmacy\_order\_created
Sent when a new pharmacy order is created and assigned to your pharmacy.
Pharmacy orders are created for over-the-counter (OTC) purchases too, not only for prescription-based orders — any fulfillment routed to your pharmacy triggers this event, whether or not it includes a prescription. For an OTC-only order, `data.doctor_data` and `data.prescription_file` are `null`, and `data.prepaid` is `0`.
`data.shipments` is always present. On create it is an empty array — pharmacies have not shipped yet. After the pharmacy adds parcels (Pharmacy Portal, or `complete_order` with `tracking_links`), later `pharmacy_order_updated` deliveries include those shipments.
**Possible `status` values:** `init`, `waiting for pharmacy`, `pending review`, `in-progress`, `ready_for_pickup`, `completed`
### Payload Example
```json theme={null}
{
"event_type": "pharmacy_order_created",
"timestamp": 1711900000,
"payload_version": "1",
"data": {
"uid": "po-abc123",
"status": "init",
"name": "#1001",
"data": {},
"external_status": "OPEN",
"created_at": 1711899000,
"updated_at": 1711899000,
"deleted_at": null,
"pharmacy": {
"uid": "ph-xyz",
"display_name": "City Pharmacy"
},
"order": {
"uid": "ord-123",
"delivery_address": {
"first_name": "Max",
"last_name": "Mustermann",
"street": "Hauptstr.",
"house_number": "1",
"zip_code": "10115",
"city": "Berlin",
"country": "Germany",
"additional_address": null,
"province": null
},
"invoice_address": {
"first_name": "Max",
"last_name": "Mustermann",
"street": "Hauptstr.",
"house_number": "1",
"zip_code": "10115",
"city": "Berlin",
"country": "Germany",
"additional_address": null,
"province": null
},
"shipping_costs_amount": 499,
"shipping_costs_currency": "EUR",
"priority": 5
},
"delivery_type": {
"uid": "dt-001",
"display_name": "Standard Shipping",
"identifier": "standard"
},
"shop_shipping_methods": [
{
"uid": "ssm-001",
"display_name": "DHL Standard",
"external_id": "shopify-standard",
"pharmacy_mapping": {
"pharmacy_uid": "ph-xyz",
"shipping_method_identifier_for_pharmacy": "DHL_STANDARD"
}
}
],
"order_items": [
{
"uid": "oi-789",
"amount": 1,
"sku": {
"uid": "sku-456",
"display_name": "Medication X 100mg",
"pzn": "12345678",
"product_uid": "prod-789",
"product_display_name": "Medication X",
"standard_selling_unit": 1.0,
"unit": "ml",
"product_handle": "medication-x"
},
"pharmacy_sku": {
"uid": "psku-456",
"external_id": "EXT-001",
"price": 1299,
"stock": 50
},
"total_paid_amount": 1299
}
],
"payouts": [
{
"status": "projected",
"amount": 999,
"currency": "EUR",
"component_type": "item_rest",
"routing_description": "Medication X 100mg item rest",
"provider_route_id": null,
"pharmacy_order_uid": "po-abc123",
"pharmacy_order_name": "#1001",
"pharmacy_order_created_at": 1711899000,
"routing_created_at": null
},
{
"status": "projected",
"amount": 300,
"currency": "EUR",
"component_type": "item_markup",
"routing_description": "Medication X 100mg markup",
"provider_route_id": null,
"pharmacy_order_uid": "po-abc123",
"pharmacy_order_name": "#1001",
"pharmacy_order_created_at": 1711899000,
"routing_created_at": null
},
{
"status": "projected",
"amount": 499,
"currency": "EUR",
"component_type": "shipping",
"routing_description": "Standard Shipping",
"provider_route_id": null,
"pharmacy_order_uid": "po-abc123",
"pharmacy_order_name": "#1001",
"pharmacy_order_created_at": 1711899000,
"routing_created_at": null
}
],
"patient_data": {
"uid": "pat-123",
"display_name": "Max Mustermann",
"email": "max@example.com",
"date_of_birth": "15.06.1990",
"phone_number": "+49 170 1234567"
},
"doctor_data": {
"uid": "doc-456",
"display_name": "Dr. Schmidt"
},
"prescription_file": {
"filename": "prescription_001.pdf",
"content_base64": "JVBERi0xLjQK..."
},
"prepaid": 1,
"shipments": []
}
}
```
### Field Reference
| Field | Type | Description |
| --------------------------------------------------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `data.uid` | string | Pharmacy order UID |
| `data.status` | string | Current order status |
| `data.name` | string or null | Human-readable order name (e.g. `#1001`) |
| `data.data` | object | Custom data attached to the order |
| `data.external_status` | string | External status identifier |
| `data.created_at` | integer | Unix timestamp of creation |
| `data.updated_at` | integer | Unix timestamp of last update |
| `data.deleted_at` | integer or null | Unix timestamp of soft-deletion, or `null` |
| `data.pharmacy` | object | Pharmacy summary with `uid` and `display_name` |
| `data.order` | object | Parent order with `uid`, `delivery_address`, and `invoice_address` |
| `data.order.shipping_costs_amount` | integer | Shipping costs in cents. Always an integer — it is `0` (never `null`) when no shipping is charged. |
| `data.order.shipping_costs_currency` | string | ISO 4217 currency code for shipping costs, such as `EUR`. |
| `data.order.priority` | integer | Priority hint for handling order sooner. Higher = more urgent. `0` means no special priority. |
| `data.delivery_type` | object or null | Delivery type with `uid`, `display_name`, and `identifier`. Resolved from the connected shop's fulfillment method (configured per shop), otherwise from the receiving pharmacy's default delivery type. `null` when neither is set — do not assume a delivery type is always present. |
| `data.shop_shipping_methods` | array | Shop shipping methods attached to the connected shop order. Empty array when the order has no shop shipping methods. |
| `data.shop_shipping_methods[].uid` | string | Shop shipping method UID |
| `data.shop_shipping_methods[].display_name` | string | Shop shipping method display name as configured on the shop |
| `data.shop_shipping_methods[].external_id` | string or null | Identifier of the shipping method on the connected shop (e.g. the Shopify shipping line code) |
| `data.shop_shipping_methods[].pharmacy_mapping` | object or null | Per-pharmacy mapping configured in the RxScale pharmacy settings, or `null` when no mapping has been configured for the receiving pharmacy yet. Configure this in the pharmacy *Settings → Shipping method mappings* screen. |
| `data.shop_shipping_methods[].pharmacy_mapping.pharmacy_uid` | string | UID of the receiving pharmacy this mapping applies to |
| `data.shop_shipping_methods[].pharmacy_mapping.shipping_method_identifier_for_pharmacy` | string | Pharmacy-specific identifier the receiving pharmacy expects for this shipping method (e.g. `DHL_STANDARD`). Use this value when handing the order off to your downstream shipping/labelling system. |
| `data.order_items` | array | Items in the order |
| `data.order_items[].sku` | object | SKU info including `pzn`, `product_uid`, `product_display_name`, `standard_selling_unit`, `unit`, and `product_handle` |
| `data.order_items[].sku.standard_selling_unit` | number or null | Standard selling unit for the SKU |
| `data.order_items[].sku.unit` | string or null | Unit for the SKU, such as `ml` or `g` |
| `data.order_items[].pharmacy_sku` | object or null | Pharmacy-specific SKU data with `uid`, `external_id`, `price` (cents), `stock`. `price` is the order-time snapshot — the price captured when the order was placed — so it does not drift if the pharmacy later changes its list price. |
| `data.order_items[].total_paid_amount` | integer | Amount the patient paid for this line item, in cents (gross). Populated for prepaid orders (those with a signed physical prescription, i.e. `prepaid: 1`); `0` when no payment applies to the line. For prepaid orders this is the price to reconcile against, rather than `pharmacy_sku.price`. |
| `data.payouts` | array | Payout components for this pharmacy order where the current pharmacy is the receiver. Paid physical-prescription orders include `projected` previews based on current values and routing configuration. Completed orders include `routed` payouts once a persisted split-payment route exists. |
| `data.payouts[].status` | string | `projected` for an indicative preview, or `routed` for a persisted split-payment route created after pharmacy order completion. |
| `data.payouts[].amount` | integer | Payout amount in cents. |
| `data.payouts[].currency` | string | ISO 4217 currency code, such as `EUR`. |
| `data.payouts[].component_type` | string | Payout component. Possible values are `item_rest`, `item_markup`, and `shipping`. |
| `data.payouts[].routing_description` | string or null | Human-readable description of the routed or projected component. |
| `data.payouts[].provider_route_id` | string or null | Payment provider route identifier. Populated for routed payouts when the provider returned an identifier, and `null` for projected payouts. |
| `data.payouts[].pharmacy_order_uid` | string | UID of the related pharmacy order. |
| `data.payouts[].pharmacy_order_name` | string or null | Human-readable pharmacy order name. |
| `data.payouts[].pharmacy_order_created_at` | integer | Unix timestamp when the pharmacy order was created. |
| `data.payouts[].routing_created_at` | integer or null | Unix timestamp when the split-payment route was created. `null` for `projected` payouts. |
| `data.patient_data` | object or null | Patient info with `uid`, `display_name`, `email`, `date_of_birth`, `phone_number` |
| `data.doctor_data` | object or null | Doctor who signed the prescription, with `uid` and `display_name` |
| `data.prescription_file` | object or null | Signed prescription PDF with `filename` and `content_base64` |
| `data.prepaid` | integer | `1` if the order has a physical (prepaid) prescription, `0` otherwise |
| `data.shipments` | array | Parcels recorded on this pharmacy order. Empty (`[]`) until the pharmacy adds a shipment. See [Shipments](#shipments). |
**`shipping_costs_amount` and `unpaid_shipping_costs_amount` are different fields — the `0`-never-`null` guarantee applies only to the first.**
`data.order.shipping_costs_amount` is always an integer: `0` when no shipping is charged, never `null`.
`unpaid_shipping_costs_amount` is a separate, internal shipping-cost override that is **nullable** and is not part of the documented webhook contract. If you see it in a payload, do not apply the `shipping_costs_amount` guarantee to it: it may be absent or `null`, and `null` there does not mean "no shipping costs". Use `shipping_costs_amount` for shipping reconciliation, and handle `unpaid_shipping_costs_amount` as an optional, possibly-`null` value if you read it at all.
Actual payment routing happens only when the pharmacy order is completed. `projected` payouts in webhook payloads are indicative, only shown for paid physical-prescription orders, and can change before completion. `routed` payout values are only populated after completion and routing exists.
***
## pharmacy\_order\_updated
Sent when an existing pharmacy order changes. That includes:
* Status transitions (for example `in-progress` → `completed`, or a cancellation).
* The pharmacy adding one or more shipments — from the Pharmacy Portal, or by completing the order through the External Pharmacy API with `tracking_links`.
The payload structure is identical to `pharmacy_order_created`. The `status` field reflects the current status, and `data.shipments` lists every shipment currently on the order (not only the one just added).
There is no separate `pharmacy_order_shipment_created` event. Subscribe to `pharmacy_order_updated` and read `data.shipments`.
### Payload Example
```json theme={null}
{
"event_type": "pharmacy_order_updated",
"timestamp": 1711910000,
"payload_version": "1",
"data": {
"uid": "po-abc123",
"status": "in-progress",
"name": "#1001",
"data": {},
"external_status": "IN_PROGRESS",
"created_at": 1711899000,
"updated_at": 1711910000,
"deleted_at": null,
"pharmacy": {
"uid": "ph-xyz",
"display_name": "City Pharmacy"
},
"order": {
"uid": "ord-123",
"delivery_address": {
"first_name": "Max",
"last_name": "Mustermann",
"street": "Hauptstr.",
"house_number": "1",
"zip_code": "10115",
"city": "Berlin",
"country": "Germany",
"additional_address": null,
"province": null
},
"invoice_address": {
"first_name": "Max",
"last_name": "Mustermann",
"street": "Hauptstr.",
"house_number": "1",
"zip_code": "10115",
"city": "Berlin",
"country": "Germany",
"additional_address": null,
"province": null
},
"shipping_costs_amount": 499,
"shipping_costs_currency": "EUR",
"priority": 5
},
"delivery_type": {
"uid": "dt-001",
"display_name": "Standard Shipping",
"identifier": "standard"
},
"shop_shipping_methods": [
{
"uid": "ssm-001",
"display_name": "DHL Standard",
"external_id": "shopify-standard",
"pharmacy_mapping": {
"pharmacy_uid": "ph-xyz",
"shipping_method_identifier_for_pharmacy": "DHL_STANDARD"
}
}
],
"order_items": [
{
"uid": "oi-789",
"amount": 1,
"sku": {
"uid": "sku-456",
"display_name": "Medication X 100mg",
"pzn": "12345678",
"product_uid": "prod-789",
"product_display_name": "Medication X",
"standard_selling_unit": 1.0,
"unit": "ml",
"product_handle": "medication-x"
},
"pharmacy_sku": {
"uid": "psku-456",
"external_id": "EXT-001",
"price": 1299,
"stock": 50
},
"total_paid_amount": 1299
}
],
"payouts": [
{
"status": "projected",
"amount": 999,
"currency": "EUR",
"component_type": "item_rest",
"routing_description": "Medication X 100mg item rest",
"provider_route_id": null,
"pharmacy_order_uid": "po-abc123",
"pharmacy_order_name": "#1001",
"pharmacy_order_created_at": 1711899000,
"routing_created_at": null
},
{
"status": "projected",
"amount": 300,
"currency": "EUR",
"component_type": "item_markup",
"routing_description": "Medication X 100mg markup",
"provider_route_id": null,
"pharmacy_order_uid": "po-abc123",
"pharmacy_order_name": "#1001",
"pharmacy_order_created_at": 1711899000,
"routing_created_at": null
},
{
"status": "projected",
"amount": 499,
"currency": "EUR",
"component_type": "shipping",
"routing_description": "Standard Shipping",
"provider_route_id": null,
"pharmacy_order_uid": "po-abc123",
"pharmacy_order_name": "#1001",
"pharmacy_order_created_at": 1711899000,
"routing_created_at": null
}
],
"patient_data": {
"uid": "pat-123",
"display_name": "Max Mustermann",
"email": "max@example.com",
"date_of_birth": "15.06.1990",
"phone_number": "+49 170 1234567"
},
"doctor_data": {
"uid": "doc-456",
"display_name": "Dr. Schmidt"
},
"prescription_file": {
"filename": "prescription_001.pdf",
"content_base64": "JVBERi0xLjQK..."
},
"prepaid": 1,
"shipments": [
{
"uid": "pos-001",
"shipment_reference": "#1001-1",
"sequence_number": 1,
"carrier_name": "DHL",
"tracking_number": "00340434782962080954",
"tracking_url": "https://www.dhl.de/de/privatkunden/pakete-empfangen/verfolgen.html?piececode=00340434782962080954",
"service": "V01PAK",
"return_tracking_number": null,
"return_label_expiry": null,
"is_test": false,
"created_at": 1711908000
}
]
}
}
```
### Shipments
`data.shipments` is the list of parcels the pharmacy has recorded on this order. Use it to pick up tracking details when a pharmacy ships — including split shipments (more than one parcel).
**When the array is filled**
* Completing the order via `PATCH /v1/external_pharmacy_api/pharmacy_orders/{uid}/complete_order` with `tracking_links` creates a shipment and then sends `pharmacy_order_updated`.
* Adding a shipment in the Pharmacy Portal (manual tracking, or a booked carrier label that is followed by an order update) also leaves the shipment on the order. The next `pharmacy_order_updated` payload includes **all** current shipments, ordered by `sequence_number`.
* `pharmacy_order_created` almost always has `"shipments": []`. Do not treat a missing tracking URL on create as an error.
**How to consume it**
* Treat `uid` as the stable identity of a parcel. A later `pharmacy_order_updated` may add further entries; existing UIDs stay the same.
* Prefer `tracking_url` for customer-facing tracking. `tracking_number` and `carrier_name` are present when the pharmacy or carrier supplied them; either can be `null` on a newly created placeholder shipment.
* `is_test` is `true` only when the parcel was booked against a carrier sandbox (no real dispatch). Ignore test shipments for live fulfillment.
* `return_tracking_number` and `return_label_expiry` (`YYYY-MM-DD`) are set only after a return label is issued; otherwise they are `null`.
| Field | Type | Description |
| ----------------------------------------- | -------------- | ------------------------------------------------------------------------------ |
| `data.shipments[].uid` | string | Shipment UID |
| `data.shipments[].shipment_reference` | string | Human-readable reference, typically `{order name}-{sequence}` (e.g. `#1001-1`) |
| `data.shipments[].sequence_number` | integer | 1-based parcel index on this pharmacy order |
| `data.shipments[].carrier_name` | string | Carrier name, such as `DHL`, `DPD`, `UPS`, `Hermes`, `FedEx`, or `Other` |
| `data.shipments[].tracking_number` | string or null | Carrier tracking number, when known |
| `data.shipments[].tracking_url` | string or null | HTTPS tracking URL, when known |
| `data.shipments[].service` | string or null | Carrier product/service code, when known (e.g. `V01PAK`) |
| `data.shipments[].return_tracking_number` | string or null | Return-label tracking number, if a return label was issued |
| `data.shipments[].return_label_expiry` | string or null | Return-label expiry date as `YYYY-MM-DD`, or `null` |
| `data.shipments[].is_test` | boolean | `true` if this shipment was booked in carrier test mode |
| `data.shipments[].created_at` | integer | Unix timestamp when the shipment was recorded |
Internal label-file locations are not included in the webhook. Use `tracking_url` / `tracking_number` for tracking, not storage paths.
### Organisation-Level Webhooks
When the webhook subscription was created via the Management API (organisation-level), order events include additional data:
* The `sku` objects inside `order_items` and `fulfillment.items` are enriched with Shopify identifiers (`shop_variation_id`, `shop_product_external_id`).
* A `fulfillment` object is added with fulfillment order details.
```json theme={null}
{
"event_type": "pharmacy_order_updated",
"timestamp": 1711910000,
"payload_version": "1",
"data": {
"uid": "po-abc123",
"status": "in-progress",
"...": "... same fields as above ...",
"order_items": [
{
"uid": "oi-789",
"amount": 1,
"sku": {
"uid": "sku-456",
"display_name": "Medication X 100mg",
"pzn": "12345678",
"product_uid": "prod-789",
"product_display_name": "Medication X",
"standard_selling_unit": 1.0,
"unit": "ml",
"product_handle": "medication-x",
"shop_variation_id": "48372910234",
"shop_product_external_id": "93847261045"
},
"pharmacy_sku": {
"uid": "psku-456",
"external_id": "EXT-001",
"price": 1299,
"stock": 50
},
"total_paid_amount": 1299
}
],
"fulfillment": {
"uid": "fo-abc123",
"external_id": "EXT-FO-001",
"items": [
{
"uid": "foi-001",
"amount": 1,
"status": "init",
"external_id": "EXT-FOI-001",
"order_item": {
"uid": "oi-789",
"amount": 1,
"sku": {
"uid": "sku-456",
"display_name": "Medication X 100mg",
"pzn": "12345678",
"product_uid": "prod-789",
"product_display_name": "Medication X",
"standard_selling_unit": 1.0,
"unit": "ml",
"product_handle": "medication-x",
"shop_variation_id": "48372910234",
"shop_product_external_id": "93847261045"
}
}
}
],
"order": {
"uid": "ord-123",
"shop_order": {
"uid": "so-456",
"external_id": "SHOP-789",
"shop_identifier": "my-shopify-store"
}
}
}
}
}
```
| Field | Type | Description |
| ------------------------------------------------------------------ | -------------- | --------------------------------------------- |
| `data.order_items[].sku.shop_variation_id` | string or null | Shopify variant ID |
| `data.order_items[].sku.shop_product_external_id` | string or null | Shopify product ID |
| `data.fulfillment.uid` | string | Fulfillment order UID |
| `data.fulfillment.external_id` | string or null | External identifier for the fulfillment order |
| `data.fulfillment.items[].order_item.sku.shop_variation_id` | string or null | Shopify variant ID |
| `data.fulfillment.items[].order_item.sku.shop_product_external_id` | string or null | Shopify product ID |
| `data.fulfillment.order.uid` | string | Parent order UID |
| `data.fulfillment.order.shop_order.uid` | string | Shop order UID |
| `data.fulfillment.order.shop_order.external_id` | string or null | External shop order identifier |
| `data.fulfillment.order.shop_order.shop_identifier` | string | Shop identifier |
The `fulfillment` field and the Shopify identifiers (`shop_variation_id`, `shop_product_external_id`) inside `sku` objects are only present in organisation-level webhook deliveries. Pharmacy-level webhooks do not include these fields.
***
## pharmacy\_sku\_stock\_updated
Sent when a pharmacy SKU's stock level changes. This includes:
* Direct stock updates via the Pharmacy API (`PATCH /v1/.../pharmacy_skus/{uid}/stock` or inventory endpoints).
* Automatic stock reduction when a pharmacy order is completed. This fires for both manual completion (via the pharmacy UI / API) **and** automatic completion when RxScale detects the order has reached a shipped or completed state via its status-check integration with the pharmacy's backend. One event is emitted per pharmacy SKU on the completed order.
### Payload Example
```json theme={null}
{
"event_type": "pharmacy_sku_stock_updated",
"timestamp": 1711900000,
"payload_version": "1",
"data": {
"uid": "psku-abc123",
"pharmacy_uid": "ph-xyz",
"sku_uid": "sku-456",
"external_id": "EXT-001",
"price": 1299,
"stock": 45,
"reserved_amount": 7,
"markup": 0,
"priority": 0,
"type": "default",
"created_at": 1711800000,
"updated_at": 1711900000,
"deleted_at": null,
"sku": {
"uid": "sku-456",
"display_name": "Medication X 100mg",
"pzn": "12345678",
"product_uid": "prod-789",
"product_display_name": "Medication X",
"standard_selling_unit": 1.0,
"unit": "ml",
"product_handle": "medication-x"
}
}
}
```
### Field Reference
| Field | Type | Description |
| -------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `data.uid` | string | Pharmacy SKU UID |
| `data.pharmacy_uid` | string | Pharmacy UID |
| `data.sku_uid` | string | SKU UID |
| `data.external_id` | string or null | External identifier in your own system |
| `data.price` | integer | Price in cents |
| `data.stock` | integer | Current stock level |
| `data.reserved_amount` | integer | Units reserved against this SKU — sum of order item amounts across all in-flight pharmacy orders (i.e. orders not yet completed or cancelled). Useful to derive available stock as `stock - reserved_amount`. |
| `data.markup` | integer | Platform markup in cents. **Webhook-only:** the External Pharmacy REST SKU endpoints deliberately omit `markup`, `priority`, and `type` (platform margin). Do not expect the same fields on `GET`/`PATCH` pharmacy SKUs. |
| `data.priority` | integer | Priority level (webhook-only; omitted from REST SKU responses) |
| `data.type` | string | Pharmacy SKU type (webhook-only; omitted from REST SKU responses) |
| `data.created_at` | integer | Unix timestamp of creation |
| `data.updated_at` | integer | Unix timestamp of last update |
| `data.deleted_at` | integer or null | Unix timestamp of soft-deletion, or `null` |
| `data.sku` | object | Nested SKU and product information |
| `data.sku.uid` | string | SKU UID |
| `data.sku.display_name` | string | SKU display name |
| `data.sku.pzn` | string | Pharmazentralnummer (PZN) |
| `data.sku.product_uid` | string | Parent product UID |
| `data.sku.product_display_name` | string or null | Display name of the parent product |
| `data.sku.standard_selling_unit` | number or null | Standard selling unit for the SKU |
| `data.sku.unit` | string or null | Unit for the SKU, such as `ml` or `g` |
| `data.sku.product_handle` | string or null | URL handle/slug of the parent product |
### Organisation-Level Webhooks
When the webhook subscription was created via the Management API (organisation-level), stock events include additional data:
* The `sku` object is enriched with Shopify identifiers (`shop_variation_id`, `shop_product_external_id`).
* A `shop_identifier` field is added at the `data` level.
```json theme={null}
{
"event_type": "pharmacy_sku_stock_updated",
"timestamp": 1711900000,
"payload_version": "1",
"data": {
"uid": "psku-abc123",
"pharmacy_uid": "ph-xyz",
"sku_uid": "sku-456",
"external_id": "EXT-001",
"price": 1299,
"stock": 45,
"reserved_amount": 7,
"markup": 0,
"priority": 0,
"type": "default",
"created_at": 1711800000,
"updated_at": 1711900000,
"deleted_at": null,
"shop_identifier": "my-shopify-store",
"sku": {
"uid": "sku-456",
"display_name": "Medication X 100mg",
"pzn": "12345678",
"product_uid": "prod-789",
"product_display_name": "Medication X",
"standard_selling_unit": 1.0,
"unit": "ml",
"product_handle": "medication-x",
"shop_variation_id": "48372910234",
"shop_product_external_id": "93847261045"
}
}
}
```
| Field | Type | Description |
| ----------------------------------- | -------------- | ------------------ |
| `data.shop_identifier` | string or null | Shop identifier |
| `data.sku.shop_variation_id` | string or null | Shopify variant ID |
| `data.sku.shop_product_external_id` | string or null | Shopify product ID |
The `shop_identifier` field and the Shopify identifiers (`shop_variation_id`, `shop_product_external_id`) inside the `sku` object are only present in organisation-level webhook deliveries. Pharmacy-level webhooks do not include these fields.
***
## appointment\_reminder\_due
Sent when a configured appointment reminder becomes due. This event is delivered to organisation-level webhook subscriptions only.
Reminders can also carry direct action links for the patient (join, reschedule, cancel) via RxScale's own email/SMS content. The `rebook_allowed` and `cancel_allowed` fields below tell you whether those actions are currently available — they are only ever `true` when `recipient_role` is `patient`.
### Payload Example
```json theme={null}
{
"event_type": "appointment_reminder_due",
"timestamp": 1893452400,
"payload_version": "1",
"data": {
"appointment_uid": "appt-e2e-123",
"organisation_uid": "org-abc123",
"shop_uid": "shop-e2e-1",
"appointment_type_reminder_uid": "reminder-e2e-1",
"recipient_role": "doctor",
"recipient_email": "doctor@example.com",
"recipient_phone_number": "+491701234567",
"recipient_display_name": "Dr. Grace Hopper",
"patient_uid": "patient-e2e-1",
"patient_display_name": "Ada Lovelace",
"doctor_uid": "doctor-e2e-1",
"doctor_display_name": "Dr. Grace Hopper",
"appointment_type_uid": "type-e2e-1",
"appointment_type_name": "Consultation",
"visit_reason": "Medication review before changing dosage",
"start_date": 1893456000,
"end_date": 1893457800,
"estimated_duration_minutes": 30,
"minutes_before": 60,
"rebook_allowed": false,
"cancel_allowed": false,
"send_email": true,
"send_sms": false
}
}
```
### Field Reference
| Field | Type | Description |
| ------------------------------------ | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data.appointment_uid` | string | Scheduled appointment UID |
| `data.organisation_uid` | string | UID of the organisation the appointment belongs to |
| `data.shop_uid` | string or null | Shop UID associated with the patient, if available |
| `data.appointment_type_reminder_uid` | string | Reminder rule UID |
| `data.recipient_role` | string | Recipient role: `patient`, `doctor`, or `admin` |
| `data.recipient_email` | string or null | Resolved recipient email, if available |
| `data.recipient_phone_number` | string or null | Resolved recipient phone number, if available |
| `data.recipient_display_name` | string or null | Resolved recipient display name |
| `data.patient_uid` | string | Patient profile UID |
| `data.patient_display_name` | string | Patient display name |
| `data.doctor_uid` | string | Doctor UID |
| `data.doctor_display_name` | string | Doctor display name |
| `data.appointment_type_uid` | string or null | Appointment type UID |
| `data.appointment_type_name` | string or null | Appointment type name |
| `data.visit_reason` | string or null | Optional reason, supplied when the hold was created or updated at confirm |
| `data.start_date` | integer | Appointment start time (Unix timestamp) |
| `data.end_date` | integer or null | Appointment end time (Unix timestamp), if known |
| `data.estimated_duration_minutes` | integer or null | The appointment's estimated duration, in minutes |
| `data.minutes_before` | integer | Reminder offset in minutes before the appointment |
| `data.rebook_allowed` | boolean | Whether the patient can currently reschedule via the reminder's action link. Only ever `true` when `recipient_role` is `patient`, the appointment type allows patient rebooking, and timing rules (e.g. minimum notice) are currently met |
| `data.cancel_allowed` | boolean | Whether the patient can currently cancel via the reminder's action link. Only ever `true` when `recipient_role` is `patient` and timing rules (e.g. minimum notice) are currently met |
| `data.send_email` | boolean | Whether RxScale's notification handler should send email for this reminder |
| `data.send_sms` | boolean | Whether RxScale's notification handler should send SMS for this reminder |
***
## patient\_doctor\_meeting\_updated
Sent when a patient-doctor meeting changes lifecycle state. This event is delivered to organisation-level webhook subscriptions only (registered via the Management API).
**Possible `change` values:**
| Value | When emitted |
| ----------- | --------------------------------------------------------------------------------------------------------------- |
| `held` | A scheduled appointment was held (the meeting window opened) |
| `confirmed` | A scheduled appointment was confirmed |
| `expired` | A scheduled appointment expired without being joined |
| `cancelled` | A meeting was cancelled |
| `rebooked` | A meeting was rebooked (a new meeting replaced this one) |
| `completed` | A meeting was completed — an on-demand meeting ended, or a doctor recorded a confirmed appointment as completed |
| `no_show` | A doctor recorded a confirmed appointment as a no-show (the patient did not attend) |
Use `change` as the authoritative indicator of what happened. `status` reflects the meeting's current database status and is `null` for on-demand meetings.
### Payload Example
```json theme={null}
{
"event_type": "patient_doctor_meeting_updated",
"timestamp": 1893456000,
"payload_version": "1",
"data": {
"organisation_uid": "org-abc123",
"meeting_uid": "m-e2e-1",
"change": "confirmed",
"status": "confirmed",
"meeting_type": "consultation",
"meeting_format": "digital",
"start_date": 1893456000,
"end_date": 1893456900,
"estimated_duration_minutes": 15,
"confirmed_at": 1893450000,
"cancelled_at": null,
"cancellation_reason": null,
"visit_reason": "Medication review before changing dosage",
"expires_at": null,
"previous_meeting_uid": null,
"appointment_type_uid": "at-1",
"doctor_uid": "doc-1",
"patient_profile_uid": "pat-1"
}
}
```
### Field Reference
| Field | Type | Description |
| --------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `data.organisation_uid` | string | UID of the organisation the meeting belongs to |
| `data.meeting_uid` | string | Unique identifier for the meeting |
| `data.change` | string | The lifecycle transition that triggered this event — see the table above |
| `data.status` | string or null | Current status of the meeting. `null` for on-demand meetings; use `change` for logic instead |
| `data.meeting_type` | string | Type of meeting (e.g. `consultation`) |
| `data.meeting_format` | string | Format of the meeting (e.g. `digital`) |
| `data.start_date` | integer or null | Unix timestamp of the scheduled start (scheduled appointments only) |
| `data.end_date` | integer or null | Unix timestamp of the planned end for scheduled appointments. This is the originally scheduled end time, not the actual end |
| `data.estimated_duration_minutes` | integer or null | Estimated duration in minutes |
| `data.confirmed_at` | integer or null | Unix timestamp of when the meeting was confirmed, or `null` |
| `data.cancelled_at` | integer or null | Unix timestamp of cancellation, or `null` |
| `data.cancellation_reason` | string or null | Reason for cancellation, or `null` |
| `data.visit_reason` | string or null | Optional reason, supplied when the hold was created or updated at confirm. May also appear in reminder content and synced calendar invite descriptions |
| `data.expires_at` | integer or null | Unix timestamp after which the meeting link expires, or `null` |
| `data.previous_meeting_uid` | string or null | UID of the meeting this one was rebooked from, or `null`. Set when `change` is `rebooked` |
| `data.appointment_type_uid` | string or null | UID of the appointment type (scheduled appointments only) |
| `data.doctor_uid` | string or null | UID of the assigned doctor |
| `data.patient_profile_uid` | string | UID of the patient profile. This is the only patient identifier in the payload — no PII (name, date of birth, or contact details) is included |
### Subscribing
Subscribe via the Management API to receive this event:
```bash theme={null}
curl -X POST "https://api.rxscale.com/v1/management/notification-subscriptions/" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"notification_type": "patient_doctor_meeting_updated",
"target": "https://your-system.com/webhooks/rxscale"
}'
```
### Email notifications
Separately from webhooks, an organisation can have RxScale email the patient, the doctor, and/or
its admins when a meeting is confirmed, cancelled, or rescheduled. Admins configure that from the
Notifications tab in the admin portal, and it is off by default. `confirmed` is subscribable
independently of the other two changes — enabling it does not require also enabling cancellation
or rescheduling emails.
**This does not change webhook behaviour.** The event still fires on every transition listed
above, with the same payload, whether or not any email subscription exists. The webhook payload
itself is unchanged by this feature.
One difference is worth planning for: a reschedule publishes **two** events — `cancelled` for the
old meeting and `rebooked` for the new one — but sends at most **one** email, the rescheduling
one. If you mirror this stream into your own patient messaging, apply the same rule, or the
patient hears "cancelled" and then "moved". `confirmed` is not part of a reschedule and has no
such pairing: it publishes once, for a fresh booking, and sends at most one confirmation email.
### Delivery and Idempotency
Webhooks are delivered at least once. Your endpoint should treat deliveries as idempotent using the combination of `data.meeting_uid` and `data.change` as the unique key — retried deliveries will carry the same values.
# Overview
Source: https://docs.rxscale.com/webhooks/overview
Receive real-time notifications via webhooks
# Webhooks
Webhooks let you receive real-time HTTP notifications when events happen in RxScale. Instead of polling for changes, register a webhook URL and RxScale will send you a POST request whenever an event occurs.
## How Webhooks Work
1. **Register** a webhook subscription with your target URL and desired event type.
2. **Receive** POST requests to your URL when events occur.
3. **Verify** the webhook signature to ensure the request is authentic.
4. **Respond** with a `2xx` status code to acknowledge receipt.
## Webhook Payload Format
All webhook payloads follow the same envelope structure:
```json theme={null}
{
"event_type": "pharmacy_order_created",
"timestamp": 1711700000,
"payload_version": "1",
"data": {
// Event-specific data
}
}
```
| Field | Type | Description |
| ----------------- | ------- | ---------------------------------------------------------------- |
| `event_type` | string | The type of event that occurred |
| `timestamp` | integer | Unix timestamp of when the event was generated |
| `payload_version` | string | Schema version of the payload (currently `"1"`) |
| `data` | object | Event-specific data — see [Events](/webhooks/events) for details |
## Registering Webhooks
You can register webhooks through:
* **Pharmacy Portal** — Navigate to API Access in the sidebar. If you manage multiple pharmacy groups, select the group from the dropdown at the top of the page.
* **External Pharmacy API** — `POST /v1/external_pharmacy_api/webhooks/`
* **Management API** — `POST /v1/management/notification-subscriptions/`
Registering through the **External Pharmacy API** (or the Pharmacy Portal) returns a `webhook_secret` in the creation response. It is shown only once — store it securely, because you need it to verify payload signatures.
Organisation subscriptions created through the **Management API** are authenticated with a custom request header you configure yourself instead of a `webhook_secret`.
## Retry Policy
If your endpoint does not respond with a `2xx` status code within **30 seconds** (or times out), RxScale will retry the delivery with exponential backoff. Respond quickly and process heavy work asynchronously.
# Security
Source: https://docs.rxscale.com/webhooks/security
Verify webhook signatures to ensure authenticity
# Webhook Security
Every webhook delivery includes a signature that you should verify to ensure the request originated from RxScale and was not tampered with.
## Signature Verification
Each webhook request includes a signature header. Verify it by computing an HMAC-SHA256 hash of the request body using the `webhook_secret` returned when you registered the subscription.
### Example (Python)
```python theme={null}
import hmac
import hashlib
def verify_webhook(payload_body: bytes, signature: str, webhook_secret: str) -> bool:
expected = hmac.new(
webhook_secret.encode(),
payload_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
```
### Example (Node.js)
```javascript theme={null}
const crypto = require('crypto');
function verifyWebhook(payloadBody, signature, webhookSecret) {
const expected = crypto
.createHmac('sha256', webhookSecret)
.update(payloadBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
```
## Best Practices
Never process webhook payloads without verifying the signature first. This protects against spoofed requests.
Always use `hmac.compare_digest` (Python) or `crypto.timingSafeEqual` (Node.js) to prevent timing attacks.
You must return a `2xx` response within the delivery timeout of **30 seconds**. A slower response (or no response) counts as a failed delivery and is retried with exponential backoff — see the [Retry Policy](/webhooks/overview#retry-policy).
We nonetheless *recommend* acknowledging much faster than that — a good target is under 5 seconds — by returning `2xx` as soon as you have stored the payload and doing any heavy processing asynchronously afterwards. The 5 seconds is a recommendation, not a requirement; only the 30-second timeout is enforced.
Webhook deliveries may be retried. Use the `event_type` + `timestamp` + `data.uid` to deduplicate events.