Skip to content

Instantly share code, notes, and snippets.

@givp
Last active August 7, 2026 03:05
Show Gist options
  • Select an option

  • Save givp/1f44a04b11941c70ff56e0bb76ab67ba to your computer and use it in GitHub Desktop.

Select an option

Save givp/1f44a04b11941c70ff56e0bb76ab67ba to your computer and use it in GitHub Desktop.
RC Authenticate API contract

Authenticate API contract

External integration contract for RingCX to retrieve a patient's outbound-calling rules at call setup time.


Overview

Item Value
Method POST
Path /api-ext/patients/authenticate
Content-Type application/json
Authentication Shared API key (AUTHENTICATE_API_KEY)
Purpose Validate a patient's PIN and return their wards, devices, allowed numbers, and kill-switch flags

The enforcement system (RingCX) is the PEP (policy enforcement point). This API is the PDP (policy decision point). RingCX calls this endpoint during call setup and must act on the response (allow, block, record) according to its own scripting — that wiring is out of scope for this repository.


Authentication

Every request must include the shared API key in the Authorization header.

Accepted formats (case-sensitive prefix, then the key):

Header value Accepted
Bearer <api-key> Yes
ApiKey <api-key> Yes
APIKey <api-key> Yes
<api-key> (raw, no prefix) Yes
Status Condition Response body
401 Missing or invalid API key { "error": "Invalid API key" }
503 Server has no AUTHENTICATE_API_KEY configured { "error": "Authenticate endpoint is not configured" }

Provision the key out-of-band. In production it is stored as the AUTHENTICATE_API_KEY environment variable (Azure Key Vault / app settings recommended).


Request

Body

{
  "patient_id": "12345",
  "pin": "0000"
}
Field Type Rules
patient_id string Required. Numeric string only (/^\d+$/). Maps to patients.external_patient_id in the database.
pin string Required. 4–8 digits (/^\d{4,8}$/). Compared to the stored patient PIN.

Validation errors (400)

{ "error": "patient_id must be a numeric string" }
{ "error": "pin must be 4-8 digits" }

Responses

Authentication denied (403)

Returned when:

  • No patient exists with the given patient_id, or
  • The PIN does not match

The response is intentionally generic — it does not reveal whether the patient ID or PIN was wrong.

{
  "patient_id": "",
  "any_calls_allowed": false
}

RingCX should treat this as do not allow outbound calling for the caller.

Authentication success (200)

{
  "patient_name": "Jane Smith",
  "patient_id": "12345",
  "any_calls_allowed": true,
  "ward": [
    {
      "ward_name": "Ward A",
      "any_calls_allowed": true,
      "device_dids": [
        {
          "device": {
            "did": "+61290000001",
            "any_calls_allowed": true
          }
        }
      ]
    }
  ],
  "allowed_callers": [
    {
      "phone_number": "+61290000099",
      "name": "Jane Smith",
      "relationship": "Mother",
      "any_calls_allowed": true,
      "allow_recording": false
    },
    {
      "phone_number": "+61290000100",
      "name": "John Smith",
      "relationship": "Brother",
      "any_calls_allowed": false,
      "allow_recording": true
    }
  ],
  "global_whitelist": [
    {
      "phone_number": "+61131114",
      "name": "Quit Smoking",
      "any_calls_allowed": true
    },
    {
      "phone_number": "+611800011511",
      "name": "Mental Health hotlines",
      "any_calls_allowed": true
    }
  ]
}

Top-level fields

Field Type Description
patient_name string Patient's display name (patients.full_name).
patient_id string External patient ID (same as request patient_id).
any_calls_allowed boolean Patient-level outbound kill switch. false = block all outbound calling for this patient. Also false when the global kill switch is on (see below).
ward array Wards the patient belongs to, with nested devices.
allowed_callers array Whitelisted outbound destinations for this patient. Every configured number is returned; use any_calls_allowed on each item to allow or block calls to that destination.
global_whitelist array Organisation-wide outbound destinations dialable by every patient. See global_whitelist[] items below.

ward[] items

Field Type Description
ward_name string Ward name.
any_calls_allowed boolean Ward-level kill switch. false = block outbound for this ward.
device_dids array Devices in this ward. May be empty if the group has no devices.

ward[].device_dids[] items

Field Type Description
device.did string Device phone number (E.164).
device.any_calls_allowed boolean Device-level kill switch. false = block outbound on this device.

allowed_callers[] items

Each item is one whitelisted destination stored in allowed_numbers. All fields are always present on success responses.

Field Type Description
phone_number string E.164 destination on the patient's whitelist.
name string Contact name for this destination (may be empty).
relationship string Contact relationship to the patient (may be empty).
any_calls_allowed boolean Per-number outbound kill switch. true = patient may dial this number (subject to ward/device/global rules). false = block outbound to this number even though it remains on the whitelist. Defaults to true when an administrator adds a number.
allow_recording boolean Whether recording is allowed for calls to this number. Independent of any_calls_allowed.

Enforcement notes for RingCX:

  • Top-level any_calls_allowed must be true before any outbound call can be permitted for this patient (alongside ward, device, allowed-number, and global rules).
  • A destination must appear in allowed_callers and have any_calls_allowed: true before an outbound call to that number can be permitted.
  • Numbers with any_calls_allowed: false are still returned so the enforcement layer can distinguish "not whitelisted" from "whitelisted but blocked".
  • allow_recording only affects recording behaviour; it does not grant dialling permission on its own.
  • A destination may also appear in global_whitelist. Every patient may dial those numbers when top-level any_calls_allowed is true and the destination has any_calls_allowed: true, without needing a matching entry in allowed_callers.

global_whitelist[] items

Each item is one organisation-wide destination stored in global_whitelist_numbers. All fields are always present on success responses.

Field Type Description
phone_number string E.164 destination on the global whitelist.
name string Display label for this destination (may be empty).
any_calls_allowed boolean Per-number outbound kill switch. true = patient may dial this number (subject to global rules). false = block outbound to this number even though it remains on the global whitelist. Defaults to true when an administrator adds a number.

Enforcement notes for RingCX:

  • When top-level any_calls_allowed is true, a patient may dial a number that appears in global_whitelist with any_calls_allowed: true even if it is not in allowed_callers.
  • Numbers with any_calls_allowed: false are still returned so the enforcement layer can distinguish "not on global whitelist" from "on global whitelist but blocked".
  • When the global kill switch is enabled, global_whitelist is returned unchanged (each item keeps its any_calls_allowed value) but top-level any_calls_allowed is false. Enforcement must treat this as no outbound allowed regardless of global_whitelist flags — the same pattern as allowed_callers.

Kill switches

Global kill switch

If the administrator has enabled the global outbound kill switch (system_settings.outbound_calling_blocked = true), the API still returns 200 on valid credentials but forces:

  • top-level any_calls_allowedfalse
  • every ward[].any_calls_allowedfalse
  • every ward[].device_dids[].device.any_calls_allowedfalse

allowed_callers is returned unchanged (including each item's any_calls_allowed value). global_whitelist is also returned unchanged. Enforcement logic must still respect the global block (treat as no outbound allowed regardless of ward, device, allowed-number, or global-whitelist flags).

Patient kill switch

If the administrator has disabled outbound calling for a patient (patients.any_calls_allowed = false), the API still returns 200 on valid credentials but forces:

  • top-level any_calls_allowedfalse
  • every ward[].any_calls_allowedfalse
  • every ward[].device_dids[].device.any_calls_allowedfalse

allowed_callers is returned unchanged. Enforcement logic must treat the patient as unable to place outbound calls regardless of ward, device, or allowed-number flags.

When both the global and patient kill switches apply, the effective result is the same: no outbound calling is permitted.

Server error (500)

{ "error": "Failed to authenticate patient" }

HTTP status summary

Status Meaning
200 Valid patient_id + PIN; rules payload returned.
400 Malformed request (patient_id or pin validation failed).
401 Invalid or missing API key.
403 Valid request shape but authentication denied (unknown patient or wrong PIN).
500 Unexpected server/database error.
503 Authenticate endpoint not configured on the server.

Examples

Success

curl -s -X POST https://<host>/api-ext/patients/authenticate \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <api-key>" \
  -d '{"patient_id":"12345","pin":"0000"}'

Denied (wrong PIN or unknown patient)

curl -s -o /dev/null -w "%{http_code}" -X POST https://<host>/api-ext/patients/authenticate \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <api-key>" \
  -d '{"patient_id":"12345","pin":"9999"}'
# → 403

Invalid API key

curl -s -X POST https://<host>/api-ext/patients/authenticate \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer wrong-key" \
  -d '{"patient_id":"12345","pin":"0000"}'
# → 401  { "error": "Invalid API key" }

Operational expectations

This endpoint sits on the call path. RingCX will call it during call setup.

Topic Guidance
Latency Target low, predictable response times. Avoid retries that delay call setup unnecessarily.
Availability Outages affect whether calls can be placed. Fail-open vs fail-closed when this API is unreachable is a business decision — not defined in this repo. Document the chosen behaviour in the RingCX engagement.
TLS Production must use HTTPS.
Idempotency Read-only with respect to call policy (no side effects on success). Safe to retry on transport errors if RingCX deduplicates.
Rate limiting Not implemented in the API today. Size RingCX concurrency accordingly.

Field naming reference

Every JSON property in this contract is snake_case, at every level of nesting, in both request and response bodies. Integrators must match the names exactly.

Area Properties
Request patient_id, pin
Top-level success patient_name, patient_id, any_calls_allowed, ward, allowed_callers, global_whitelist
Wards / devices ward_name, any_calls_allowed, device_dids, device.did, device.any_calls_allowed
Allowed numbers phone_number, name, relationship, any_calls_allowed, allow_recording
Global whitelist phone_number, name, any_calls_allowed
Denied response patient_id, any_calls_allowed
Errors error

Note: any_calls_allowed appears at five levels — patient (top-level), ward, device, allowed-number, and global-whitelist. These are independent flags that happen to share a name. This document always qualifies which one is meant (for example "top-level any_calls_allowed"); rely on the nesting, not the name alone.

Changed from the previous revision

Earlier revisions mixed camelCase, snake_case and one PascalCase field, carried over from a legacy consumer that is no longer supported. Those spellings are removed, not aliased — the old names are neither accepted nor returned.

Old New
AnyCallsAllowed (denied response) any_calls_allowed
anyCallsAllowed (patient, ward, device) any_calls_allowed
wardName ward_name
deviceDIDs device_dids

Request fields (patient_id, pin), the allowed-number and global-whitelist items, and the error body were already snake_case and are unchanged.


Out of scope (RingCX engagement)

The following are not specified here and belong to the separate RingCX integration:

  • When during call setup to call this endpoint
  • How to map ward / device_dids / allowed_callers (including any_calls_allowed and allow_recording) to allow/block/record actions
  • Precedence when ward-level, device-level, and allowed-number kill switches conflict
  • Behaviour when the API times out or returns 500 / 503
  • Inbound call handling (this system governs outbound only)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment