* → show this message. Tag rules match both synced and custom tags (case-insensitive).
- **Door visibility** supports **No doors visible** and **Has visible doors**. It uses the mobile app's final door list: usable and future/outside-hours doors count as visible, as do doors deliberately shown in a blocked or maintenance state. **No doors visible** matches only when that list is empty.
- **Rule order is priority** — the first enabled rule that matches a person wins; one automated message per person.
- When a person stops matching every rule, their automated message clears itself automatically.
- The message text supports the placeholders **\{firstName\}** and **\{lastName\}** — e.g. *"Hi \{firstName\}, your membership has expired."*
### Adding a rule
1. Click **Add rule**, or pick from **Start from a template…**:
| Template | Matches | Pre-filled message (severity) |
|----------|---------|-------------------------------|
| **No door access** | Door visibility: no doors visible | *No door access available* (Notice) |
| **Expired membership** | Membership status: expired | *Your membership has expired* (Notice) |
| **Payment overdue** | Tag: overdue | *Payment overdue* (Notice) |
| **Account on hold / suspended** | Membership status: on hold, suspended | *Your account is on hold* (Urgent) |
| **Welcome (new member)** | Tag: new | *Welcome!* (Information) |
| **Trial / prospect** | Person type: prospect | *Enjoying your trial?* (Information) |
| **Document / waiver required** | Tag: waiver-required | *Action required* (Notice) |
2. Set the **When** field and values, edit the message, and ensure the **Rule enabled** toggle is on.
3. Click **Save changes** in the settings save bar.
### What happens after saving
Saving triggers a background sweep that applies the rules to everyone at the facility. The card shows progress: *"Applying to members in the background…"*, then *"App messages applied · N updated"* (or *"Background update failed."*). Decoupled users are skipped by the sweep, and the rules also apply to users as they're synced or edited going forward.
The mobile API re-evaluates automated rules against current door visibility whenever facilities are loaded. Policy, grant, maintenance, and schedule changes therefore affect the message immediately; the app does not wait for another background sweep.
### Turning automation off for one person
On a user's panel, when an automated message is active you can click **Turn off for this user** — the automated message is removed and no rule will ever message them again (until you click **Re-enable automated messages** there). Useful for exceptions, e.g. a member with a billing dispute you're handling personally.
## What members see
Messages appear as cards at the top of the app dashboard, coloured by type, with the title, body, and optional link button. Urgent messages are visually prominent. Messages are informational — they do **not** block door access by themselves (use the **Access enabled** switch or Access Policies for that).
## Related pages
- [Users](https://docs.performancehub.co/module-docs/door-gate-access/user-access) — per-user messages, the Message badge
- [Access Policies](https://docs.performancehub.co/module-docs/door-gate-access/access-rules) — door authorisation by Who, Where, and When
- [App Security & Settings](https://docs.performancehub.co/module-docs/door-gate-access/app-security) — the facility-wide message card
- [Mobile app guide](https://docs.performancehub.co/module-docs/door-gate-access/mobile-app)
Source: https://docs.performancehub.co/module-docs/door-gate-access/app-messaging
---
# App Security & Settings
The **App Security & Settings** page controls how the mobile app behaves on members' devices: whether it demands re-authentication (app lock), when support contact details are shown, and the facility-wide dashboard message. As the page itself notes: *"These don't affect server-side access decisions; they're applied client-side by the mobile app."* Find it under **Settings → App Security & Settings**.

*Figure 1: App security, location support contacts and the facility-wide message*
## App lock
App lock forces users to re-authenticate with their device — Face ID, Touch ID, Android biometrics, or the device passcode — before they can use the app to unlock doors. It protects against a lost or borrowed phone being used to enter your facility.
### Enforce app lock for this facility
*"When on, everyone with access to this facility must authenticate with their device before unlocking any door."*
- **Default: off.** Members can still enable app lock voluntarily in their own app settings; this toggle makes it mandatory.
- When enforced, members cannot turn it off in the app.
- Members who haven't set up a screen lock on their device are asked to set one up before they can continue.
### Grace period before re-prompting
*"Tap the app, leave for less than this, come back and you won't be re-prompted. Cold launches and longer absences always require re-authentication."*
| Option | Behaviour |
|--------|-----------|
| **Always prompt** | Every return to the app requires authentication |
| **1 minute** | Re-prompt after 1 minute away |
| **5 minutes** (default) | Re-prompt after 5 minutes away |
| **15 minutes** | Re-prompt after 15 minutes away |
| **30 minutes** | Re-prompt after 30 minutes away |
| **1 hour** | Re-prompt after 1 hour away |
A short grace period is more secure; a longer one is less intrusive for members who check the app frequently.
### Users signed in to multiple facilities
*"If a user is signed into multiple facilities, the strictest setting across them is applied (shortest grace period wins)."* Concretely: app lock is enforced if **any** of the user's facilities enforces it, and the grace period used is the **shortest** among the enforcing facilities.
## Location support contacts
Controls when the facility's support contact details (phone/email) appear on the app dashboard so members can reach you when something's wrong:
| Option | Behaviour |
|--------|-----------|
| **Smart (default)** | *"Support contacts appear automatically when a door is offline or there is limited information in the UI."* — shown only when members likely need help |
| **Always show** | Contact details are always on the dashboard |
| **Never show** | Contact details are never shown |
## Facility-wide message
The third card on this page is the facility-wide app message — an announcement shown to everyone at this facility on the app dashboard. It uses the standard message editor (type, title, body, optional link, schedule); see [App Messaging](https://docs.performancehub.co/module-docs/door-gate-access/app-messaging) for the full editor reference.
## Saving
All changes on this page go through the settings save bar — click **Save changes** to apply. App-side settings (app lock, support contacts, messages) are picked up by members' apps when they next refresh.
## Related pages
- [App Messaging](https://docs.performancehub.co/module-docs/door-gate-access/app-messaging)
- [Mobile app guide](https://docs.performancehub.co/module-docs/door-gate-access/mobile-app) — the member's view of app lock
- [Reference](https://docs.performancehub.co/module-docs/door-gate-access/reference) — defaults table
Source: https://docs.performancehub.co/module-docs/door-gate-access/app-security
---
# Access Logs
The Access Logs tab is the audit trail: every unlock attempt — successful, denied, or failed; from the mobile app or by an admin — is recorded with who, which door, when, where from, and the outcome. This page covers reading the log, filtering, the No visit recorded view, exporting to CSV, and the event detail view.

*Figure 1: The Access Logs tab*
Events are retained for approximately **13 months**, then removed automatically. Deleting a user does **not** delete their events — history is kept for the audit trail.
Access Logs are separate from [Audit Logs](https://docs.performancehub.co/module-docs/audit-logs/overview), which record requests made across Performance Hub.
## Filtering and searching

*Figure 2: Search, filter chips, columns, export, live refresh and range*
| Control | What it does |
|---------|--------------|
| **Search** | *"Search door, person, event ID…"* — matches door, person, event ID, command ID, state, and denial reason |
| **View** | **Door events** (the audit trail) or **No visit recorded** (see [below](#no-visit-recorded)) |
| **State**, **Result**, **Door**, **Source**, **Partner**, **Reason** | Filter chips that narrow the list by event state, outcome, door, where the request came from, integration partner, and denial reason |
| **Columns** | Choose which columns the table shows; the button shows how many are on |
| **Export CSV** | Downloads the currently filtered events (see [Exporting to CSV](#exporting-to-csv)) |
| **Live** | Refreshes the list automatically, with a countdown to the next refresh; the button beside it refreshes now |
| **Range** | **Last day**, **Last 7 days**, **Last 30 days**, **Last 90 days**, or custom dates |
The header shows the event count for the current range and filters. Each range loads up to the 500 most recent events.
## Event states
| State chip | Meaning |
|-----------|---------|
| **Confirmed unlocked** | The door controller confirmed the relay released — the door actually unlocked |
| **Unlocked** | The unlock was reported successful |
| **Admin Unlocked** | An admin triggered the unlock from Performance Hub (admin override). Recorded as soon as the command is dispatched |
| **Denied** | The request was rejected — see the **Reason** column ([all reasons](https://docs.performancehub.co/module-docs/door-gate-access/reference)) |
| **Pending** / **Request received** | The request is still being processed (transient — normally resolves within seconds) |
| **Timed out** | The unlock command was sent but the controller didn't confirm in time — usually connectivity trouble at the door |
| **Failed** | The unlock could not be completed due to an error |
| **Locked by staff** | Staff used **Lock now** while the auto-unlock schedule was holding the door open. A schedule event, not an unlock attempt |
| **Schedule resumed** | The auto-unlock schedule took over again, either at its next change or because staff pressed **Resume schedule**. A schedule event, not an unlock attempt |
Each row shows the timestamp, state, door, person, distance from the door and command latency (when available), and the denial reason for denied events.
## No visit recorded

*Figure 3: The No visit recorded view*
Set **View** to **No visit recorded** to list one-off Access Time Windows, added by an admin or an integration, where the person never unlocked a door during the window (including any [grace period](https://docs.performancehub.co/module-docs/door-gate-access/access-time-windows#grace-periods-and-timezone)). Columns: **Person**, **Scheduled period**, **Source / partner**, **Status**. Records are kept for about 13 months.
For example, an office can see which contractors with a one-off window never turned up.
## Access Policy attribution
Policy-authorised events identify the named policy and the matched audience, target, and schedule. Scheduled matches can also include the active concrete start and end.
Administrator-owned and integration-owned Access Time Windows keep their own attribution paths (`admin_time_window` and `source_time_window`); they are not relabelled as Access Policies. Policy denials distinguish target, audience, and inactive-schedule mismatches. A future occurrence uses `outside_access_time_window` with structured availability when known.
## Event details
Click any row to open the **Access Details** dialog.

*Figure 4: The Access Details dialog for a member unlock*
The header shows the door, its location, the outcome, an **Admin override** badge when applicable, and the denial reason if denied. Fields shown when available:
| Field | Notes |
|-------|-------|
| **Date / Time** | When the attempt happened |
| **Coordinates** | The location the phone reported, with GPS accuracy (±N m); copyable |
| **Door / Gate** | Door name and location |
| **Person** or **Admin** | Who attempted — admin unlocks show the admin's account |
| **Device** | The phone/device used |
| **IP Address** | The requesting IP; copyable |
| **Hold time** | How long the door stayed unlocked |
| **Distance from door** | Computed distance from the geofence centre |
| **Event ID / Command ID** | Copyable identifiers for support escalation |
| **Latency** | How long the controller took to respond to the command |
| **Authorization source** | Access Policy, permanent grant, administrator window, or integration window |
| **Policy** | Policy ID/name plus the matched audience, door/Access Scope target, and Anytime/Scheduled mode |
| **Window / Source ID** | Bounded attribution for active scheduled or source windows |
| **Active from / to** | Concrete Unix-second occurrence used for a window decision; the end is exclusive |
**Unlock Location** plots the reported coordinates on a map with an **Open in Google Maps** link. When there are no coordinates you'll see *"No location captured — unlocked from the Performance Hub."* (admin overrides) or *"No location data was reported for this unlock attempt."*
## Exporting to CSV
Click **Export CSV** to download the **currently filtered** events. The file includes identity, door, result, denial, controller, integration, and policy/window attribution fields. The button is disabled when the filtered list is empty. In the **No visit recorded** view, **Export CSV** downloads that list instead.
## Tips
- The Doors tab's **Recent activity** card is a 24-hour shortcut into this log — clicking an event there jumps straight to it here.
- Investigating a member complaint? Use the **State** or **Result** chip to show denied attempts, search their name, and read the reason — then see [Troubleshooting](https://docs.performancehub.co/module-docs/door-gate-access/troubleshooting) for the fix per reason.
- For policy complaints, confirm the policy name and its Who/Where/When. For integration windows, also inspect source age and `scope_key` mapping.
- A burst of **Timed out** events on one door usually means that door's controller has network problems — check its status in the [Doors](https://docs.performancehub.co/module-docs/door-gate-access/doors) tab.
- Your [AI Agent](https://docs.performancehub.co/module-docs/ai-agent/overview) can read door events through MCP, for example for an [office lobby welcome for each visitor](https://docs.performancehub.co/module-docs/display-screens/published-apps#office-lobby-welcome-for-each-visitor). Door events carry a user ID, not a name or date of birth.
## Related pages
- [Reference](https://docs.performancehub.co/module-docs/door-gate-access/reference) — every state and denial reason
- [Access Policy Schedules](https://docs.performancehub.co/module-docs/door-gate-access/access-time-windows)
- [Troubleshooting](https://docs.performancehub.co/module-docs/door-gate-access/troubleshooting)
- [Doors](https://docs.performancehub.co/module-docs/door-gate-access/doors)
Source: https://docs.performancehub.co/module-docs/door-gate-access/access-logs
---
# Reference
Exhaustive tables for Door & Gate Access: every event state, denial reason, setting, default, limit, and option value in one place. Use this page to look up exactly what a value means; the linked guide pages explain how to use each feature.
## Access event states
Shown in the [Access Logs](https://docs.performancehub.co/module-docs/door-gate-access/access-logs):
| State | Meaning |
|-------|---------|
| **Request received** | The unlock request arrived and is being evaluated (transient) |
| **Pending** | The request passed checks and is awaiting dispatch (transient) |
| **Unlocked** | The unlock was reported successful |
| **Confirmed unlocked** | The door controller confirmed the relay released |
| **Admin Unlocked** | An admin triggered the unlock from Performance Hub (admin override); logged when the command is dispatched |
| **Denied** | The request was rejected — see the denial reason on the event |
| **Timed out** | The command was sent but the controller didn't confirm in time |
| **Failed** | The unlock could not be completed due to an error |
| **Locked by staff** | Staff used **Lock now** while the auto-unlock schedule was holding the door open (a schedule event, not an unlock attempt) |
| **Schedule resumed** | The auto-unlock schedule took over again (a schedule event, not an unlock attempt) |
Event lifecycle: received → dispatched → confirmed unlocked / denied / timed out / failed.
## Denial reasons
Each denied/failed unlock records a reason. The table maps the reason to what the member sees and how to fix it:
| Reason (as logged) | Member-facing message | Cause | Fix |
|--------------------|------------------------|-------|-----|
| `access_disabled` | "Your access has been suspended — please contact the facility" | The user's **Access enabled** switch is off | Re-enable it on the user's panel in [Users](https://docs.performancehub.co/module-docs/door-gate-access/user-access) |
| `no_matching_policy_or_grant` | "You don't have access to this door" | No Access Policy matched Who AND Where AND When and no additive grant path allowed access | Review [Access Policies](https://docs.performancehub.co/module-docs/door-gate-access/access-rules), user attributes, targets/exclusions, schedule, and grants |
| `no_matching_policy_target` | "You don't have access to this door" | No enabled Access Policy targets this door for the request | Add the door or its Access Scope to a policy, or use an all-doors target |
| `no_matching_policy_audience` | "You don't have access to this door" | Policies target the door, but none match the person's type/status or tags | Review the policy's **Who** section and the Users record |
| `outside_policy_schedule` | "Outside allowed access hours" | Who and Where match, but all matching policy schedules are inactive or expired | Review the policy's **When** section and facility timezone |
| `outside_geofence` | "Move closer — you're outside the location's geofence." | The reported location is beyond *radius + GPS accuracy + buffer* from the geofence centre | Member moves closer; or review the [geofence](https://docs.performancehub.co/module-docs/door-gate-access/geofencing) radius/pin |
| `gps_too_imprecise` | "Couldn't pin down your location accurately. Try again outdoors." | The phone's GPS accuracy is worse than the **Require GPS accuracy better than** threshold | Member retries outdoors / away from interference; or raise the threshold |
| `location_unavailable` | "Couldn't read your location. Make sure Location is enabled and set to Precise, then try again." | The phone didn't report a usable location | Member enables Location (Precise) for the app |
| `geofence_misconfigured` | Unlock denied | Geofence is enabled but its centre/pin is missing — the system fails closed | Place the geofence pin in [Settings → Geofencing](https://docs.performancehub.co/module-docs/door-gate-access/geofencing) |
| `device_offline` | "Door is offline. Please contact the facility." | The door controller hasn't reported in (no heartbeat within 5 minutes) | Restore power/network to the controller — see [Troubleshooting](https://docs.performancehub.co/module-docs/door-gate-access/troubleshooting) |
| `user_deleted` | "You don't have access…" | The Users record was deleted | Re-create or restore the user |
| `outside_access_time_window` | Door disabled with next availability when known | A future window is the user's only potential allow path and is not active yet | Check the supplied availability, timezone, source freshness, and mapping; Access Policies and grants still apply independently |
Some app versions show the broader "You don't have access to this door" or "Outside allowed access hours" message for policy-specific denials. Access Logs retain the detailed reason for administrators.
## Access Policies
| Property | Meaning |
|----------|---------|
| Policy shape | Unique name, optional description, enabled flag, Who audience, Where targets/exclusions, When schedule |
| Policy match | Who AND Where AND When |
| Multiple policies | OR; any matching enabled policy allows |
| Who | Person type plus optional statuses, OR synced/custom tags; empty statuses = any status |
| Where | All facility doors, Access Scopes, individual doors, or combinations; exclusions win within that policy |
| When | Anytime, or ORed Weekly/One-time windows |
| Other allow paths | Permanent grants and administrator/integration windows stay separate and additive |
| Safety | Deleted/disabled/sync-blocked users, door deletion/block, maintenance, geofence, rate limits, and controller checks still apply |
| Existing configuration | Imported automatically into equivalent named policies with explicit Who, Where, and When |
## Policy attribution
Access Logs identify a matching policy by name and can show its audience, target, and schedule. Scheduled matches can also include the active concrete start and end.
## Access Time Window reference
| Property | Meaning |
|----------|---------|
| Authorisation | Policy schedule windows are the policy's When; administrator/integration windows are separate additive allow paths |
| Absolute boundary | Integer Unix-second UTC instants, `[from, to)` |
| Absolute timezone | Display/audit metadata; does not shift epochs |
| Weekly boundary | Local `HH:mm` blocks with exclusive ends |
| Weekly timezone | Valid IANA timezone driving wall-clock day/time and DST |
| Weekly bounds | Optional Unix-second active-from/active-until; end exclusive |
| Overnight/overlap | Not accepted; split overnight periods across days and keep blocks non-overlapping |
| User ownership | Administrator and each integration source are isolated; one cannot erase another |
| Integration scope | Stable source `scope_key`, default `default`, mapped to PH targets; unknown/unmapped fails closed |
| Source freshness | Configured per integration cadence; stale-source behaviour must be explicit |
| Safety | Never bypasses disabled/deleted/conflicted users, `blockAllAccess`, maintenance, geofence, rate limits, or controller checks |
Initial limits: 50 windows per policy/grant/source container; 90-day maximum absolute span; 120 characters for IDs/labels/scope keys; eight weekly blocks per day; newly written entries cannot already be expired by more than one hour.
## Settings, defaults, and ranges
### Door settings
| Setting | Default | Range / options |
|---------|---------|-----------------|
| Hold time | 10 seconds | 1–30 seconds |
| Maintenance mode | Normal | Normal / Force open / Force closed |
| Door open sound | Off | On / Off |
| Door location label | — | Free text, max 64 characters |
| Icon | Door | 10 options (below) |
| Auto-unlock schedule | Off | Facility staffed hours or Custom schedule, facility timezone |
| Auto-unlock Unlock / Lock timing | At opening / At closing | At opening / At closing, or 5, 10, 15, 30, 45, 60, 90 or 120 minutes before or after |
| Auto-unlock on public holidays | Ignore holidays | Follow the facility's holiday hours / Stay locked all day / Ignore holidays |
| Access authorisation (per door) | Access Policies | Lists matching policies and permits `blockAllAccess` |
| Geofence (per door) | Inherit from facility | Inherit from facility / Override for this door / Disable geofence for this door |
| Online threshold | — | Controller counts as online with a heartbeat in the last 5 minutes |
### Door icons
| Icon | Intended use |
|------|--------------|
| Door | Standard single door (default) |
| Sliding door | Sliding / automatic door |
| Double door | Double / French door |
| Garage | Garage / roller door |
| Boom gate | Boom gate / barrier arm |
| Pedestrian | Staff / pedestrian walk-in entry |
| Security | Restricted / security door |
| Office | Office / staff-only room |
| Delivery | Loading dock / delivery bay |
| Locker | Locker / storage |
### Geofence settings
| Setting | Default | Range |
|---------|---------|-------|
| Geofence enabled | On (for a new facility configuration) | On / Off |
| Radius | 200 m | 50–5000 m (10 m steps; presets 50 / 200 / 500 / 1000 m) |
| Accuracy buffer | 25 m | — |
| Require GPS accuracy better than | 100 m | — |
| Allowance formula | — | radius + reported GPS accuracy + accuracy buffer |
### Access grace periods
Under **Settings → Access Time Windows**:
| Setting | Options |
|---------|---------|
| Allow early access | Off / 5 / 10 / 15 / 30 minutes |
| Allow access after end | Off / 5 / 10 / 15 / 30 minutes |
### App lock settings
| Setting | Default | Options |
|---------|---------|---------|
| Enforce app lock for this facility | Off | On / Off |
| Grace period before re-prompting | 5 minutes | Always prompt / 1 min / 5 min / 15 min / 30 min / 1 hour |
| Multi-facility behaviour | — | Enforced if any linked facility enforces; shortest grace period among enforcing facilities wins |
### Support contacts
| Mode | Behaviour |
|------|-----------|
| **Smart (default)** | Shown automatically when a door is offline or there is limited information in the UI |
| **Always show** | Always on the app dashboard |
| **Never show** | Never shown |
### App messages
| Field | Limit / options |
|-------|------------------|
| Notification type | Information / Notice / Urgent |
| Title | 120 characters |
| Body | 2000 characters (soft warning at 500) |
| Link URL | http(s) only, 2000 characters |
| Link label | 80 characters, default "More info" |
| Schedule | Optional Starts at / Ends at |
| Placeholders (automated rules) | `{firstName}`, `{lastName}` |
| Automated rule match fields | Membership status / Person type / Tag (tags match synced + custom, case-insensitive) / Door visibility |
| Rule priority | First enabled matching rule wins (list order) |
### User fields
| Field | Limit / notes |
|-------|----------------|
| First / last name | 80 characters each |
| Mobile phone / email | At least one required (used for OTP sign-in); phones normalised to international format |
| Tags | 80 characters per tag; synced tags overwritten by sync, custom tags admin-owned |
| Notes | 2000 characters |
| Access enabled | Default on; off = all unlocks denied |
| Grants | Facility-wide, doors list, scopes list, optional operating hours |
| Access Policies | User can be covered by one or more named policies; list/effective access uses **Access granted via policy** |
| Access Time Windows | Administrator-owned editable windows plus read-only source-owned windows; separate from policy schedules |
### Sources, statuses, and other enumerations
| Enumeration | Values |
|-------------|--------|
| User source | Manually added / External Integration / CCTV-Computer Vision / Performance Hub staff |
| User tiles | All users / Has access / No access / Not synced |
| Invitation status | Not invited / Invited / Send failed |
| Invitation recipients | Only users with door access / All eligible synced users |
| Invitation rule fields | Membership status / Person type / Tag / Sync source / Integration; operators *is any of* / *is not any of* |
| Access Logs views | Door events / No visit recorded |
| App adoption | Active (has used the app) / Not yet active |
| Require identifier (sync) | Email OR phone (default) / Email required / Phone required / Both required |
| Sync log outcomes | Added / Updated / Skipped / Disabled / Processed / Stale snapshot |
| Window states | Active / Upcoming / Expired / Stale / Unmapped |
| Membership status labels | Active, Current, Expired, On hold, Cancelled, Pending (plus any values from your source system) |
| Access Scope templates | Custom, Main entrances, Staff-only, Restricted area, 24/7 access, Business hours, Vehicle access, Service entries |
| Auto-message templates | No door access, Expired membership, Payment overdue, Account on hold / suspended, Welcome (new member), Trial / prospect, Document / waiver required |
### Sync skip reasons
| Reason shown in sync log | Meaning |
|---------------------------|---------|
| Integration sync is turned off for this facility | The source's master toggle is off |
| This integration is turned off in sync settings | The specific integration is opted out |
| No phone or email on the record (a phone or email is required to log in) | The record fails the Require identifier rule |
| Record is decoupled and managed manually | Track source updates is off for this user |
| No facility on the source record | The source record isn't linked to this facility |
| No matching user record | The change referenced a person with no row here |
## Operational limits
| Limit | Value |
|-------|-------|
| CSV import | 200 users per import |
| Bulk invitations | Sent in batches of 50 |
| Bulk delete preview sample | First 50 users shown |
| Access Logs query | Up to 500 most recent events per window |
| Access event retention | ~13 months, then removed automatically |
| Admin unlock command validity | 30 seconds (the command expires if not delivered) |
| Door controller heartbeat | Every ~30 seconds; offline after 5 minutes silence |
| Sign-in code requests | 5 per hour per phone/email (security limit, cannot be lifted) |
| App sign-out propagation | Revoked immediately; up to 15 minutes for an active session to fully end |
| Saving door settings | Reloads the controller — typically 10–15 seconds |
| No visit recorded retention | ~13 months |
| Access Time Window mappings | 50 per integration |
| Access Time Windows per container | 50 |
| One-time window span | 90 days |
| Weekly blocks | 8 per weekday |
| Window/source ID, label, scope key | 120 characters |
| Access Policies per facility | 100 |
| Total policy windows | 500 |
| Access Policy name/ID | 120 characters |
| Access Policy description | 500 characters |
| Access Policy config | 300 KiB UTF-8 |
## Related pages
- [How access decisions work](https://docs.performancehub.co/module-docs/door-gate-access/how-access-works)
- [Access Policies](https://docs.performancehub.co/module-docs/door-gate-access/access-rules)
- [Access Policy Schedules](https://docs.performancehub.co/module-docs/door-gate-access/access-time-windows)
- [Access Logs](https://docs.performancehub.co/module-docs/door-gate-access/access-logs)
- [Troubleshooting](https://docs.performancehub.co/module-docs/door-gate-access/troubleshooting)
Source: https://docs.performancehub.co/module-docs/door-gate-access/reference
---
# Troubleshooting
Symptom-oriented fixes for the most common Door & Gate Access issues. Each entry follows symptom → cause → resolution. For the meaning of any state or denial reason, see the [Reference](https://docs.performancehub.co/module-docs/door-gate-access/reference); for the underlying decision logic, see [How access decisions work](https://docs.performancehub.co/module-docs/door-gate-access/how-access-works).
> **Start with the Access Logs.** Almost every "can't get in" report has a denied event in the [Access Logs](https://docs.performancehub.co/module-docs/door-gate-access/access-logs) with the exact reason. Search the member's name, look at the most recent denied event, and jump to the matching section below.
## A door shows Offline
**Symptom:** The door card shows **Offline**, members see "Door offline", unlocks can't be sent.
**Cause:** The door controller hasn't reported in for more than 5 minutes — power or network is down at the device.
**Resolution:**
1. Check the controller has power and its network connection (Ethernet cable / Wi-Fi) — the [Hardware Troubleshooting](https://docs.performancehub.co/module-docs/door-gate-access/hardware-troubleshooting#door-shows-offline-in-the-dashboard) page covers the on-site checks (indicator LEDs, PoE splitter, cabling, SIM/antenna on 4G models).
2. Open the door's panel → **Overview** tab to see **Status** (last seen), **IP address**, and uptime — a recent "last seen" suggests an intermittent network rather than a dead device.
3. Power-cycle the controller if needed.
4. For deeper diagnostics, follow **Open in Device Management for advanced configuration**.
5. If several doors went offline at once, suspect the site's network/internet rather than the devices.
While a door is offline, members see support contact details on their dashboard (in the default **Smart** mode), so they can call you.
## A member is denied — "You don't have access to this door"
**Symptom:** Denied event with reason `no_matching_policy_or_grant`.
**Cause:** No additive path allowed them: no enabled Access Policy matched Who AND Where AND When, and no permanent grant or active administrator/integration window covered the door.
**Resolution:**
1. Open the member in **Users** and compare person type, membership status, and synced/custom tags with the policy's **Who**.
2. Check that **Where** includes the door directly, through an Access Scope, or through all facility doors, and that no exclusion removes it.
3. Check **When**: Anytime, or at least one active Weekly/One-time window. Ends are exclusive.
4. Remember policies OR together, but each policy requires Who AND Where AND When. A partial match never grants access.
5. If appropriate, use a separate permanent grant or administrator window; these remain additive.
## A member is denied — "Your access has been suspended"
**Symptom:** Denied event with reason `access_disabled`; the user shows a **Disabled** badge.
**Cause:** The user's access is off. This happens manually (an admin turned off **Access enabled**) or automatically (the member was deleted in the integration and the panel shows **Removed by integration**, or they were soft-deleted).
**Resolution:** Open the user and turn **Access enabled** back on — or, if they show **Removed by integration**, reinstate them in the source system (or decouple the row and enable access manually).
## A scheduled policy is not allowing access
**Symptom:** The app shows future availability or the log has `outside_access_time_window`; a source window appears **Stale** or **Unmapped**; or access changes at an unexpected time.
**Resolution:**
1. Confirm the policy is enabled.
2. Check the same policy's Who and Where before investigating its When schedule.
3. For a One-time window, compare the attempt with `[from, to)`. The displayed timezone cannot shift the stored instant.
4. For a Weekly window, check its IANA timezone, local weekday/block, optional active bounds, and daylight-saving transition. Ends are exclusive and overnight blocks must be split.
5. Use the Access Log's `policyId` and `policyName` to verify which named policy was evaluated.
6. Confirm `blockAllAccess`, Force closed, user disablement, geofence, and controller status are not taking precedence.
For a separate integration window, also verify logical scope mapping, source revision, source age, and delivery health. Unknown, unmapped, or stale source windows fail closed without removing Access Policy or permanent-grant paths.
Older clients can show **Outside hours** because the compatibility reason is `outside_access_hours`; the Access Log and newer clients use `outside_access_time_window` detail.
## Geofence denials — "Move closer" and friends
**Symptom:** Denied events with reasons `outside_geofence`, `gps_too_imprecise`, or `location_unavailable`.
| Reason | Cause | Resolution |
|--------|-------|------------|
| `outside_geofence` | The member's reported position is beyond *radius + GPS accuracy + buffer* from the pin | If they were genuinely on-site: check the **pin placement** and **radius** in **Settings → Geofencing** (use **Test Geofence** to simulate their spot). Watch for a pin warning that it's far from the facility's saved address. If the door shouldn't be geofenced at all (e.g. a service entry), use the door's **Disable geofence for this door** option. |
| `gps_too_imprecise` | The phone's GPS fix was worse than the **Require GPS accuracy better than** threshold (default 100 m) — common deep indoors | Have the member retry nearer a window/outdoors. If it's chronic at your site, raise the threshold slightly. |
| `location_unavailable` | The phone provided no location — Location services off, or app permission missing/approximate | The member must enable Location for the app and set it to **Precise** (the app shows an **Open Settings** shortcut). |
The denied event's detail dialog shows the reported coordinates, accuracy, and distance — invaluable for judging whether the member was actually on-site.
## Unlock sent but the door didn't open (Timed out / Failed)
**Symptom:** Events end in **Timed out** or **Failed** rather than Confirmed unlocked.
**Cause:** The command reached the server but the controller didn't confirm — flaky connectivity at the door, or a device fault.
**Resolution:**
1. Check the door's online status and device info (Overview tab). A pattern of timeouts on one door points at its network.
2. Try an admin **Unlock door** from the panel while watching the door.
3. Use **Identify device** (three beeps) to confirm you're physically at the right controller.
4. If the relay clicks but the door doesn't release, the fault is in the lock circuit — run the [jumper and multimeter tests](https://docs.performancehub.co/module-docs/door-gate-access/hardware-troubleshooting#test-the-lock-wiring-before-blaming-the-controller) in Hardware Troubleshooting.
5. Escalate via Device Management diagnostics if the device is online but not actuating.
Also check **Maintenance mode** — a door on **Force closed** stays locked for members by design. **Block all access at this door** sets explicit `blockAllAccess` and denies every member policy, permanent grant, and Access Time Window.
If the controller is online but stops responding normally, **Remotely reboot device** (door panel → **Configuration** → **Advanced**) restarts it. The door keeps its current lock state and drops offline for a minute or two while it boots.
## Member can't sign in to the app
**Symptom:** "No record found" at the Send code step.
**Cause:** The phone/email they typed doesn't match any Users record.
**Resolution:** Find their row in **Users** and compare identifiers exactly — a different number format, an old email, or a missing identifier are the usual culprits. Update the row (or have the member use the identifier on file). Remember each row needs at least one of phone/email.
**Symptom:** "Too many attempts."
**Cause:** Rate limiting — 5 code requests per hour per contact, plus a network-level limit.
**Resolution:** Wait it out; the per-contact limit is a security measure and cannot be lifted.
## Invitation problems
See the dedicated table in [Invitations → Troubleshooting](https://docs.performancehub.co/module-docs/door-gate-access/invitations). Quick hits: **Send failed** → check the address and resend; not included in bulk send → they were already invited, have used the app, lack an email, or lack access.
## Synced users missing or out of date
**Symptom:** A person exists in your member system but not in Users; or their details are stale.
**Resolution:**
1. Check **Settings → Sync settings** — is the source enabled? Is the specific integration enabled? If the card shows *N removals held back for review*, a large removal is waiting for an admin to review it.
2. Expand **Sync activity & troubleshooting** and search the person — the skip reason tells you exactly why (missing identifier, decoupled, integration off, etc.). See the [skip reason table](https://docs.performancehub.co/module-docs/door-gate-access/sync-and-integrations).
3. Check the user's **Track source updates** toggle — a decoupled row never updates from the source.
4. If the row shows **Not synced – duplicate**, another record shares the same email or phone. See [Duplicate resolution](https://docs.performancehub.co/module-docs/door-gate-access/sync-and-integrations#duplicate-resolution).
5. Use **Sync now** to force a pass instead of waiting for the daily full sync.
6. For Access Time Windows, also inspect source age, revision outcome (updated/idempotent/stale/conflict), and `scope_key` mapping. A failed fetch must leave the previous snapshot unchanged rather than clearing it.
## Auto-unlock schedule isn't opening the door
**Symptom:** The door doesn't latch open during its scheduled window.
**Resolution:**
1. Open the door's **Schedule** tab and confirm **Auto-unlock** is on. Check the **Next 14 days** preview for when the door should unlock.
2. With **Facility staffed hours**, check the facility's staffed and holiday hours in Facility & System Configuration. On a public holiday, **On public holidays** decides whether the door follows holiday hours, stays locked or ignores the holiday.
3. With **Custom schedule**, check the day actually has a time block.
4. Check whether staff used **Lock now**: the card shows *Locked by staff until HH:MM*. **Resume schedule** puts the schedule back.
5. Remember schedule changes only take effect after **saving**, which reloads the controller (typically 10–15 seconds).
6. **Maintenance mode takes precedence** — a door on Force closed won't auto-open.
7. Times are in the **facility's timezone** — verify the facility's timezone setting if windows fire at odd hours.
## An admin changed settings but nothing happened
**Symptom:** Edited policies/geofence/messages with no effect.
**Cause:** Settings-tab changes aren't applied until **Save changes** is clicked in the save bar; navigating away offers to discard them.
**Resolution:** Re-apply and save. Policy and geofence changes are used by subsequent authorisation attempts; app-side settings appear when members' apps next refresh.
## Related pages
- [Hardware Troubleshooting](https://docs.performancehub.co/module-docs/door-gate-access/hardware-troubleshooting) — power, relay, and lock wiring diagnostics
- [Access Logs](https://docs.performancehub.co/module-docs/door-gate-access/access-logs)
- [Access Policy Schedules](https://docs.performancehub.co/module-docs/door-gate-access/access-time-windows)
- [Reference](https://docs.performancehub.co/module-docs/door-gate-access/reference) — all denial reasons and defaults
- [Common questions](https://docs.performancehub.co/module-docs/door-gate-access/common-questions)
Source: https://docs.performancehub.co/module-docs/door-gate-access/troubleshooting
---
# Common Questions
Frequently asked questions about Door & Gate Access. For diagnosing specific failures, see [Troubleshooting](https://docs.performancehub.co/module-docs/door-gate-access/troubleshooting); for definitions and limits, see the [Reference](https://docs.performancehub.co/module-docs/door-gate-access/reference).
## Access and permissions
**Does adding someone in Users let them open doors?**
Not by itself. A user needs a matching [Access Policy](https://docs.performancehub.co/module-docs/door-gate-access/access-rules), a permanent grant, or a separate administrator/integration Access Time Window. Adding the row only creates the identity they sign in with.
**What's the fastest way to block someone right now?**
Turn off **Access enabled** on their row in Users. Every member unlock path is denied immediately, without editing policies or deleting the record. Optionally also **Sign out of app**.
**How do Access Policies combine?**
Every policy is allow-only. Within one policy, **Who AND Where AND When** must all match. Multiple policies are ORed, so any one matching enabled policy can grant access. An exclusion removes a door only from that policy; it does not deny another policy or grant.
**What's the difference between policies and grants?**
Policies match categories of people, doors/scopes, and schedules. Permanent grants name an individual. Administrator and integration windows are also separate paths. All are additive, while safety, block, maintenance, and geofence checks still apply.
**Why can a member open the front door but not the staff door?**
Check the matching policy's **Where** targets and exclusions, then its **Who** and **When**. Use the door's **Access Policies** tab to see policies that target it.
**Do operating hours apply to admins unlocking from Performance Hub?**
No. Admins can unlock a door remotely with **Unlock door** / **Open**; every admin unlock is recorded in Access Logs against the admin's account.
**Can I give someone access for specific hours only?**
Yes. Set **When** to Scheduled in an Access Policy and add Weekly or One-time windows. Permanent grants can retain their own hours, and administrator/integration windows remain separate additive options.
**What happens to our existing access configuration?**
It is imported into Access Policies automatically. Facility-wide access, different access for particular scopes or doors, and operating hours become explicit policy **Who**, **Where**, and **When** values. Review the imported policy names and details, but there is no manual conversion or rollout workflow.
## Doors and hardware
**Where do I find installation and wiring instructions?**
The Hardware section covers the physical side: the [Door & Gate Controller](https://docs.performancehub.co/module-docs/door-gate-access/hardware-overview) itself, [mounting](https://docs.performancehub.co/module-docs/door-gate-access/hardware-mounting), [power and networking](https://docs.performancehub.co/module-docs/door-gate-access/hardware-power-networking), the [Relay Wiring Guide](https://docs.performancehub.co/module-docs/door-gate-access/hardware-relay-wiring) for connecting strikes/maglocks/gates, and [Hardware Troubleshooting](https://docs.performancehub.co/module-docs/door-gate-access/hardware-troubleshooting).
**What does "Online" actually mean?**
The controller has reported a heartbeat within the last 5 minutes. Offline doors can't receive unlocks.
**What's the difference between the auto-unlock schedule and operating hours?**
The **When** section of an Access Policy limits when matched users can unlock with the app. The auto-unlock schedule *physically holds the door open* during its windows — no app needed. They're configured in different places and work independently. The auto-unlock schedule can follow the facility's staffed hours, so it changes when those hours change.
**Can staff lock a door early while the schedule is holding it open?**
Yes. **Lock now** locks it until the schedule's next change; **Resume schedule** puts the schedule back straight away. See [Doors](https://docs.performancehub.co/module-docs/door-gate-access/doors#lock-now-and-resume-schedule).
**What's the difference between Force closed and Block all access?**
**Force closed** is a maintenance mode that keeps the door locked and shows it to members as out of service. **Block all access** is a safety block on the door's Access Policies tab: the door works normally otherwise, but it overrides policies and per-user grants for members. Both stop member unlocks.
**Why does saving door settings restart the door controller?**
The schedule and door settings run on the controller itself (so the schedule works even if the internet drops). Saving reloads the device — typically 10–15 seconds of unavailability.
**How long does a door stay open after an unlock?**
The door's **Hold time** — 1 to 30 seconds, default 10 — then it re-locks automatically. The app shows a re-locking countdown.
## The mobile app
**Do members need a password?**
No. They sign in with a one-time code sent to the phone number or email on their Users record. See [Mobile app](https://docs.performancehub.co/module-docs/door-gate-access/mobile-app).
**Can one person use the app at multiple facilities?**
Yes. If the same phone/email exists on Users records at several facilities, the app shows a facility switcher. Note that signing them out (or app-lock policies) can span facilities because sessions are keyed to the phone/email.
**Why does the app demand "Precise" location?**
Geofenced doors are authorised server-side using the phone's reported position and accuracy. Approximate location can't prove the member is on-site, so geofenced unlocks fail without Precise.
**Can members unlock without internet on their phone?**
No — every unlock is authorised by the server in real time. (The door's *auto-unlock schedule* keeps working without internet, since it runs on the controller.)
**A member's name/phone changed — can they edit it in the app?**
They can edit their name. Phone and email are managed by admins in Users (for synced users, by the source system) because they're the sign-in identity.
## Sync, invitations, and data
**Will sync overwrite changes I make to a user?**
Sync refreshes identity fields (name, contact, type, status, synced tags) but never converts or removes Access Policies, permanent grants, custom tags, notes, or administrator windows. To freeze a row completely, turn off **Track source updates** (decouple).
**What happens when someone is removed from our member system?**
Sync removes their door access (the row stays, marked **Removed by integration**) rather than deleting them, so history is preserved. If a sync would remove an unusually large number of people at once, those removals are held back for an admin to review. See [Sync & Integrations](https://docs.performancehub.co/module-docs/door-gate-access/sync-and-integrations#removals).
**Can I export the user list?**
Yes. **Export CSV** on the Users tab downloads the currently filtered list.
**Are invitations required before someone can use the app?**
No — the invitation email is just download links and instructions. Anyone with a phone/email on file and access can install the app and sign in without ever being invited.
**How long are access logs kept?**
About 13 months, then events expire automatically. Deleting a user does not delete their events.
**Can I export the access logs?**
Yes — **Export CSV** on the Access Logs tab downloads the currently filtered events.
**Is there a limit to CSV imports?**
200 users per import; run multiple imports for larger lists. Duplicates (matching phone/email) are skipped, not duplicated.
## Settings behaviour
**I changed a setting but nothing happened.**
Settings changes require **Save changes** in the save bar. Once saved, subsequent server authorisation uses the new policy configuration; app-side settings reach phones when the app next refreshes.
**Who can see/change these settings?**
Anyone whose role includes Door & Gate Access for the facility in Performance Hub. Admin actions like unlocks are individually logged with the admin's account. Installing controller software updates also needs Device Management access, and the Performance Hub staff role filter is changed by an organisation admin.
**Does the geofence drain members' batteries?**
No background tracking is involved in unlocking — the app takes a single location fix at the moment of the unlock. (Optional arrival notifications use background location only if the member opts in.)
## Related pages
- [Overview](https://docs.performancehub.co/module-docs/door-gate-access/overview)
- [Door & Gate Controller](https://docs.performancehub.co/module-docs/door-gate-access/hardware-overview)
- [How access decisions work](https://docs.performancehub.co/module-docs/door-gate-access/how-access-works)
- [Troubleshooting](https://docs.performancehub.co/module-docs/door-gate-access/troubleshooting)
- [Reference](https://docs.performancehub.co/module-docs/door-gate-access/reference)
Source: https://docs.performancehub.co/module-docs/door-gate-access/common-questions
---
# Duress overview
Duress is your facility's duress alarm system. The people who might need to call for help discreetly carry a duress button or have one within reach. When someone presses it, Performance Hub alerts the people in your response plan, and your monitoring centre if you have one. Responders acknowledge the alarm, coordinate in Performance Hub and resolve the incident. You test the buttons on a schedule and report on how well alarms are answered. Duress suits any site where staff can face a threatening situation: a medical centre's reception, a school office, a hotel night desk, a retail counter, a bar, a council customer service desk or a gym floor.
> **Silent by design.** A duress alarm is raised without anyone nearby knowing. To keep it that way, this documentation deliberately leaves out how the buttons behave and the timings behind an alarm. Staff who use the buttons are shown the button guide in the Duress module, and admins see the timings they chose in the Duress settings.
## Quick reference
**Where to find it:** Side menu → **Duress**
**Who can access:** Users with Duress or Duress Configuration at the facility. See [Roles & permissions](https://docs.performancehub.co/module-docs/duress/roles-and-permissions).
**Purpose:** Respond to duress button alarms, run button tests and review incident history
**Requirements:** A **Display Screen Player** or an **IoT Gateway** at the facility to receive button presses. See [Before you start](#before-you-start).
## Before you start
Duress buttons need a **Display Screen Player** or an **IoT Gateway** at the facility to receive their presses. Without one, presses aren't received, and you can't add a button.
- A **Display Screen Player** receives presses when it's paired with the facility and **Duress alarm monitoring** is on in its [Connectivity & peripherals settings](https://docs.performancehub.co/module-docs/display-screens/screen-settings#connectivity--peripherals). It's on unless someone turns it off.
- An **IoT Gateway** is enabled by your account manager.
Either one is enough, and you can have more than one. Keep each button within about 10 to 15 metres of a Display Screen Player, or within an IoT Gateway's coverage. See [How presses reach Performance Hub](https://docs.performancehub.co/module-docs/duress/devices-and-triggers#how-presses-reach-performance-hub) and the [Display Screen Player](https://docs.performancehub.co/module-docs/display-screens/hardware-overview) hardware pages.
## How an alarm works
1. Someone presses a duress button.
2. An online **Display Screen Player** or **IoT Gateway** at the facility receives the press and passes it to Performance Hub. See [How presses reach Performance Hub](https://docs.performancehub.co/module-docs/duress/devices-and-triggers#how-presses-reach-performance-hub).
3. Performance Hub raises an alarm. It alerts the people in the facility's [response plan](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#response-plan), sends the alarm to your [alarm destinations](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#alarm-destinations), such as a monitoring centre or a team chat, and runs the [response actions](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#response-actions) you chose.
4. A responder acknowledges the alarm, so everyone knows someone is responding. If nobody acknowledges, the next people in the plan are alerted.
5. Responders post updates and notes on the incident, then resolve it with an outcome. See [Responding to alarms](https://docs.performancehub.co/module-docs/duress/responding-to-alarms). Response plan contacts can follow the alarm from the [alarm page](https://docs.performancehub.co/module-docs/duress/alarm-page) on their phone.
## The tabs
| Tab | What it's for |
|-----|---------------|
| **Home** | Active alarms, the last 12 months at a glance, button health, recent incidents and testing. See [Home](#home). |
| **Incidents** | Every alarm and test at the facility, with its outcome and who acknowledged it. See [The Incidents tab](https://docs.performancehub.co/module-docs/duress/responding-to-alarms#the-incidents-tab). |
| **Devices** | The facility's duress buttons: status, battery, signal and when each was last tested. See [The button list](https://docs.performancehub.co/module-docs/duress/devices-and-triggers#the-button-list). |
| **Testing** | Run a walk test or a full response drill, and book tests ahead. See [Testing & reports](https://docs.performancehub.co/module-docs/duress/testing-and-reports). |
| **Reports** | Alarm response and readiness figures, with CSV and PDF downloads. See [Reports](https://docs.performancehub.co/module-docs/duress/testing-and-reports#reports). |
| Settings (cog icon) | **Response plan**, **Response actions**, **Device alerts**, **Integrations**, **Delivery log** and **Browser Notifications**. See [Alerts & notifications](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications). |
The settings page also has a **Product Documentation** link, which opens these pages.
When an alarm is open, the other tabs show how many open alarms need a response, with **Open alarms** to go straight to them.
Alarms at your other facilities appear in Performance Hub's alarm banner across the top of the app, with a way to switch to that facility.
## Home

*Figure 1: The Duress home page: live alarms, the last 12 months, buttons and recent incidents*
| Part of Home | What it shows |
|--------------|---------------|
| Active alarms | Each active alarm appears at the top as a card with the button and the time, and **Acknowledge**, **Resolve**, **Open incident** and **Post an update**. When nothing is active, it shows **All clear**. |
| **Last 12 months** | **REAL ALARMS**, **TIME TO ACKNOWLEDGE (P50)**, **FALSE-ALARM RATE**, **TEST COVERAGE**, **DEVICE UPTIME** and **DELIVERY SUCCESS**. **Full report** opens [Reports](https://docs.performancehub.co/module-docs/duress/testing-and-reports#reports). |
| **Buttons** | How many buttons are **Online**, **Offline**, **Needs charging**, **Keeps going offline** and **Alerts muted**, and the buttons that need attention. **All buttons** opens **Devices**. See [Keeps going offline](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#keeps-going-offline) and [Mute alerts](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#mute-alerts). |
| **Recent incidents** | The latest alarms and tests, with their status. **All incidents** opens **Incidents**. |
| Testing | A summary of testing at the facility. **Open testing** opens **Testing**. |
Real alarms are rare, so Home and Reports cover the last 12 months by default.
Until alarm notifications are turned on for the computer you're using, Home shows a banner, "Duress alarm notifications are off on this computer.", with **Turn on notifications**. See [Browser notifications](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#browser-notifications).
## Duress and Duress Configuration
Duress has two modules, so the people who respond to alarms don't also need to be able to change how alarms work. In module pickers, Duress Configuration sits under Duress.
| Module | Lets people | Product description |
|--------|-------------|---------------------|
| **Duress** | Respond to alarms, see Home, Incidents and Reports, and start and end tests | "Respond to duress button alarms, run button tests and review incident history." |
| **Duress Configuration** | Set up the response plan, response actions, device alerts, integrations, button labels and links, and test schedules | "Set up duress response plans, monitoring integrations, escalation policies and test schedules." |
Turning on Duress also turns on [Device Management](https://docs.performancehub.co/module-docs/device-management/overview), which holds the buttons alongside the facility's other devices. See [Roles & permissions](https://docs.performancehub.co/module-docs/duress/roles-and-permissions) for which role templates include each module.
## What's new
Duress announcements, including the launch of the full module and button tests and incident history for every site, are listed in [What's New in 2026](https://docs.performancehub.co/module-docs/whats-new/2026#q4-2026).
## Documentation guide
| Page | What it covers |
|------|----------------|
| [Setting up Duress](https://docs.performancehub.co/module-docs/duress/setup) | The setup steps, the recommended order, [adding and testing a button](https://docs.performancehub.co/module-docs/duress/setup#add-a-button) and preparing your staff |
| [Buttons & triggers](https://docs.performancehub.co/module-docs/duress/devices-and-triggers) | How presses reach Performance Hub, the button list, [button setups](https://docs.performancehub.co/module-docs/duress/devices-and-triggers#button-setups), [locating a button](https://docs.performancehub.co/module-docs/duress/devices-and-triggers#locate-a-button) and what raises an alarm |
| [Alerts & notifications](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications) | The response plan, response actions, alarm destinations, device alerts, the delivery log and browser notifications |
| [Responding to alarms](https://docs.performancehub.co/module-docs/duress/responding-to-alarms) | Acknowledging and resolving, incidents, updates, evidence and doors |
| [Alarm page](https://docs.performancehub.co/module-docs/duress/alarm-page) | What response plan contacts and group chats see when they open an alarm's **Manage incident** link |
| [Testing & reports](https://docs.performancehub.co/module-docs/duress/testing-and-reports) | Walk tests, full response drills, test schedules, [brand test schedules](https://docs.performancehub.co/module-docs/duress/testing-and-reports#brand-test-schedules), [Organisation Duress](https://docs.performancehub.co/module-docs/duress/testing-and-reports#organisation-duress) and reports |
| [Roles & permissions](https://docs.performancehub.co/module-docs/duress/roles-and-permissions) | Who can respond, who can configure and which role templates include Duress |
| [Common questions](https://docs.performancehub.co/module-docs/duress/common-questions) | Quick answers and troubleshooting |
## Related pages
- [Device Management](https://docs.performancehub.co/module-docs/device-management/overview): every device at the facility, including duress buttons
- [Display Screens](https://docs.performancehub.co/module-docs/display-screens/overview): the Display Screen Players that receive button presses
- [Door & Gate Access](https://docs.performancehub.co/module-docs/door-gate-access/overview): the doors a duress alarm can unlock or lock down
- [CCTV / VSaaS](https://docs.performancehub.co/module-docs/cctv-vsaas/overview): the cameras linked to duress buttons
- [Brand access & security](https://docs.performancehub.co/module-docs/organisation-settings/brand-access-security#duress): a brand's duress response plans
- [Organisation Duress](https://docs.performancehub.co/module-docs/duress/testing-and-reports#organisation-duress): buttons and test schedules across the organisation
Source: https://docs.performancehub.co/module-docs/duress/overview
---
# Setting up Duress
Duress does nothing at a facility until its first button is added. This page covers the setup steps Duress shows you, the order we recommend for a new site, brand response plans, and how to prepare the people who will use and respond to the buttons.
## Quick reference
**Where to find it:** Side menu → **Duress** → **Home**
**Who can access:** Duress Configuration to set up the response plan and alerts, and Device Management to add buttons. See [Roles & permissions](https://docs.performancehub.co/module-docs/duress/roles-and-permissions).
**Purpose:** Get a facility's duress alarms working end to end, then keep staff ready
**Requirements:** A **Display Screen Player** or an **IoT Gateway** at the facility to receive button presses
## What you need first
Duress buttons need a **Display Screen Player** or an **IoT Gateway** at the facility to receive their presses. Without one, presses aren't received, and you can't add a button. Set one up first:
- **Display Screen Player:** pair it with the facility using the [pairing code](https://docs.performancehub.co/module-docs/display-screens/hardware-overview#pairing-code) on its TV, in Device Management → **Add Device**. **Duress alarm monitoring** is on in its [Connectivity & peripherals settings](https://docs.performancehub.co/module-docs/display-screens/screen-settings#connectivity--peripherals) unless someone turns it off.
- **IoT Gateway:** ask your account manager to enable one.
See [How presses reach Performance Hub](https://docs.performancehub.co/module-docs/duress/devices-and-triggers#how-presses-reach-performance-hub) for placement and for having more than one.
## Asleep until the first button
With no buttons, Duress is asleep at the facility: "nobody is texted, emailed or tested". The first time you open it, Duress shows "Welcome to Duress" with **Add your first button**, **How the buttons work** and **Skip setup for now**.
Duress wakes up as soon as the first button is added.
## The setup steps

*Figure 1: Before the first button is added, Duress shows the setup steps*
Home shows a checklist, "Finish setting up Duress", with how many steps are complete, until every step is done. **Hide setup guide** hides it.
| Step | What it asks you to do |
|------|------------------------|
| **Add your first button** | "Enter the serial on the back of the button. Duress wakes up as soon as it is added, then checks a display screen or IoT Gateway can hear it." See [Add a button](#add-a-button). |
| **Choose who gets alerted** | "Set up the response plan: who is alerted first, how, and who comes next if nobody acknowledges." See [Response plan](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#response-plan). |
| **Run a walk test** | Press every button once to check each press reaches the right people. See [Run a test](https://docs.performancehub.co/module-docs/duress/testing-and-reports#run-a-test). |
| **Turn on alarm notifications** | Turn on notifications on the computer you're using. This step is per computer, and only shows for people who can respond to alarms. See [Browser notifications](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#browser-notifications). |
If you don't have the access a step needs, the step says who to ask.
## Recommended order
For a new site, work through Duress in this order:
1. **Switch on the modules and roles.** Give responders Duress, and the people who configure alarms Duress Configuration. See [Roles & permissions](https://docs.performancehub.co/module-docs/duress/roles-and-permissions).
2. **Set up a Display Screen Player or IoT Gateway.** Pair a Display Screen Player near where the buttons will go and check **Duress alarm monitoring** is on, or ask your account manager to enable an IoT Gateway. See [How presses reach Performance Hub](https://docs.performancehub.co/module-docs/duress/devices-and-triggers#how-presses-reach-performance-hub).
3. **Add the buttons.** Give each a name and room that responders will recognise, and test each one before it goes live. See [Add a button](#add-a-button).
4. **Set the response plan:** who is alerted first, how, and who comes next. See [Response plan](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#response-plan).
5. **Choose the response actions,** such as unlocking doors or keeping footage. See [Response actions](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#response-actions).
6. **Add alarm destinations and device alerts:** your monitoring centre, a team chat or an on-call service, and who hears about low batteries and offline buttons. See [Alarm destinations](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#alarm-destinations) and [Device alerts](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#device-alerts).
7. **Turn on browser notifications** on every computer that watches for alarms. See [Browser notifications](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#browser-notifications).
8. **Run a walk test** to check every button end to end. See [Run a test](https://docs.performancehub.co/module-docs/duress/testing-and-reports#run-a-test).
9. **Book recurring tests.** See [Test schedules](https://docs.performancehub.co/module-docs/duress/testing-and-reports#test-schedules).
10. **Prepare your staff.** See [Prepare your staff](#prepare-your-staff).
> **Staff-only screens.** If you link a button to display screens, link only staff-only screens, such as a back office or a staff room. Never link screens that the public, or the person causing the threat, can see.
## Add a button
Adding a button needs Device Management at the facility. In Duress, open **Devices** and click **Add button** to open the **Add a duress/panic button** dialog. You can also start from Device Management; see [Adding devices](https://docs.performancehub.co/module-docs/device-management/adding-devices).
The dialog has three steps: **Button**, **Test** and **Done**.
### Before you start

*Figure 2: With no Display Screen Player or IoT Gateway at the facility, the dialog starts with Before you start*
You can't add a button until the facility has a Display Screen Player or IoT Gateway. Until then, the dialog opens on **Before you start**: "Duress buttons need a display screen or IoT Gateway". "Presses reach Performance Hub through a display screen or IoT Gateway at the facility. Add a display screen and turn on Duress alarm monitoring in its settings, or contact your account manager to enable an IoT Gateway." **Open Device Management** takes you there. The Display Screen Player or IoT Gateway doesn't have to be online for you to add the button, but it does to receive its presses. See [How presses reach Performance Hub](https://docs.performancehub.co/module-docs/duress/devices-and-triggers#how-presses-reach-performance-hub).
### Button details

*Figure 3: The button's serial number, name and room, with Test it before it goes live ticked*
| Field | What to enter |
|-------|---------------|
| **Serial number** | "Printed on the back of the button: 12 hex characters for a Bluetooth button or 16 for a LoRa button, with or without colons." Type it, or click **Scan QR**. |
| **Name** | "Shown in alarms so responders know which button was pressed." Name it for where it is, for example "Reception Lobby", "Consult Room 2", "Box Office" or "Pool deck". |
| **Room** | "Shown in alarms so responders know where the button was pressed." |
| **Test it before it goes live** | "The first press in the next 30 minutes is only a test: it's recorded and nobody is alerted. After that press, or after 30 minutes, presses raise real alarms." |
Leave **Test it before it goes live** ticked. Testing first proves that a press from where the button will sit reaches Performance Hub, without alerting your responders or your monitoring centre while you install it. Click **Add button**.
### Test the button

*Figure 4: Duress has added the button and a Display Screen Player hears it, and is waiting for the test press*
The **Test** step ticks off each check as it passes: "Added to this facility", then "Heard by" the Display Screen Player or IoT Gateway that hears the button. Then it asks you to press the button: "This press is only a test."
The step also counts down: "Test mode ends in" the time left. "You can close this window: test mode stays on until then."

*Figure 5: The test press was received and nobody was alerted*
When the test press arrives, the step shows the time it was received and "Nobody was alerted." Then: "The button works. From now on, presses raise real alarms." Click **Continue**.
**Skip test, go live now** ends test mode straight away, so the next press raises a real alarm. Skip the test only if you're about to run a [walk test](https://docs.performancehub.co/module-docs/duress/testing-and-reports#run-a-test) that covers this button.
A new button can take a while to be heard: "It can take up to 15 minutes to come online — if it does not, restart the facility’s display screens or IoT Gateway." If it still isn't heard, check the Display Screen Players and IoT Gateway near it. See [A button was pressed but no alert came through](https://docs.performancehub.co/module-docs/duress/common-questions#a-button-was-pressed-but-no-alert-came-through).
### Done

*Figure 6: The duress button is live*
"The duress button is live". "Next, link its cameras, doors and screens." Click **Set up this button** to open the button and set its zone and [links](https://docs.performancehub.co/module-docs/duress/devices-and-triggers#links-cameras-doors-and-screens), or **Done** to finish.
### Test mode

*Figure 7: A button in test mode, with Go live now*
While a button is in test mode, it shows **Test mode** in the button list and in its details: "Test mode: the first press before" the time shown "is only a test, so nobody is alerted. After that press, presses raise real alarms."
**Go live now** ends test mode early. Until a button leaves test mode, tell anyone who might rely on it that it isn't live yet.
## Brand response plans
A brand can have a response plan template that its facilities follow, add to or replace with their own. Brand plans live in the brand's **Duress** section in Organisation Settings. See [Brand access & security](https://docs.performancehub.co/module-docs/organisation-settings/brand-access-security#duress), and [Brand response plans](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#brand-response-plans) for what a facility can do with its brand's plan.
## Prepare your staff
A duress button only helps if people know it's there and responders know what to do.
- **Everyone who might use a button** knows where it is and has been shown the button guide in the Duress module. The guide is in the product, not in this documentation.
- **Responders know how to acknowledge and resolve** an alarm, and what to post as an update. See [Responding to alarms](https://docs.performancehub.co/module-docs/duress/responding-to-alarms).
- **Test days are announced.** Presses during a test are treated as tests, so book tests ahead and tell staff. Scheduled tests email everyone involved. See [Test schedules](https://docs.performancehub.co/module-docs/duress/testing-and-reports#test-schedules).
- **Inductions include the buttons.** Cover them in new staff inductions, and refresh everyone's training when you add or move a button.
- **There's a backup.** Agree another way to call for help for any time a button is out of action.
- **Records are kept.** Note each button's location and serial number, and its battery changes, in your [device inventory](https://docs.performancehub.co/module-docs/device-management/network-and-installation#keep-a-device-inventory).
## Related pages
- [Duress overview](https://docs.performancehub.co/module-docs/duress/overview): what Duress does and how an alarm works
- [Buttons & triggers](https://docs.performancehub.co/module-docs/duress/devices-and-triggers): Display Screen Players and IoT Gateways, adding buttons and links
- [Alerts & notifications](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications): who is alerted and how
- [Testing & reports](https://docs.performancehub.co/module-docs/duress/testing-and-reports): walk tests and test schedules
Source: https://docs.performancehub.co/module-docs/duress/setup
---
# Buttons and triggers
Duress buttons are the facility's way to call for help discreetly. This page covers how a press reaches Performance Hub, adding buttons, reading the button list, a button's details and links, and everything that can raise a duress alarm.
## Quick reference
**Where to find it:** Side menu → **Duress** → **Devices**
**Who can access:** Users with Duress or Duress Configuration at the facility can open **Devices**. Changing a button's label, room, zone and links needs Duress Configuration, and adding, moving and removing buttons needs Device Management.
**Purpose:** Add duress buttons, check they're healthy and decide what happens when each one is pressed
## How presses reach Performance Hub
A duress button has no internet connection of its own. Each press is received by an online **Display Screen Player** or **IoT Gateway** at the facility, which passes it to Performance Hub. **Without one, presses aren't received**, and you can't add a button.
| Receiver | What it needs |
|----------|---------------|
| **Display Screen Player** | A [Display Screen Player](https://docs.performancehub.co/module-docs/display-screens/hardware-overview) receives presses when it's paired with the facility and **Duress alarm monitoring** is on in its [Connectivity & peripherals settings](https://docs.performancehub.co/module-docs/display-screens/screen-settings#connectivity--peripherals). **Duress alarm monitoring** is on unless someone turns it off. Pair a player with the [pairing code](https://docs.performancehub.co/module-docs/display-screens/hardware-overview#pairing-code) on its TV, in Device Management → **Add Device**. See [Adding devices](https://docs.performancehub.co/module-docs/device-management/adding-devices). |
| **IoT Gateway** | Enabled by your account manager. Device Management shows it as "Enabled by your account manager". |

*Figure 1: Display Screen Players show Online in Device Management, in the same list as the duress buttons*

*Figure 2: Duress alarm monitoring, in a Display Screen Player's Connectivity & peripherals settings*
**Either one is enough, and more than one is supported.** Any Display Screen Player or IoT Gateway at the facility that hears a button passes its presses on, and a button's details list every one that heard it. A button only counts as offline when none of them has heard it. Where you can, have more than one near the buttons, so a press still gets through if one is offline.
A Display Screen Player or IoT Gateway belongs to the facility it's paired with. Only Display Screen Players paired with a facility count for that facility's buttons.
Depending on the button model, a button may need an **IoT Gateway**, because a Display Screen Player can't receive it. Check with your account manager when you order buttons.
### Placement
"Keep buttons within range of one: about 10 to 15 metres of a display screen, or within an IoT Gateway’s coverage." Walls, metal and distance all reduce range, so test each button from where it will be. See [Place devices with care](https://docs.performancehub.co/module-docs/device-management/network-and-installation#place-devices-with-care).
## Add a button
Before you add a button, check that a Display Screen Player with **Duress alarm monitoring** on, or an IoT Gateway, is set up near where the button will go. It doesn't have to be online for you to add the button, but it does to receive its presses. Adding a button needs Device Management at the facility.
**From Duress:** open **Devices** and click **Add button**. The **Add a duress/panic button** dialog takes you through the button's serial number, name and room, then a test press before the button goes live. See [Add a button](https://docs.performancehub.co/module-docs/duress/setup#add-a-button) in Setting up Duress.
**From Device Management:** click **Add Device**, then scan the QR code on the button or type its serial number. Or click **Or choose the device type manually** and choose **Duress/Panic Button**. See [Adding devices](https://docs.performancehub.co/module-docs/device-management/adding-devices).
Name each button for where it is, so responders can find it quickly, for example "Reception Lobby", "Consult Room 2", "Box Office" or "Bar Service Point". You can change the label and room later on its [Settings tab](#a-buttons-details).
## The button list

*Figure 3: Every duress button at the facility, with its status, setup, battery and when it was last tested*
The **Duress buttons** list shows every button at the facility. "Battery is low at 30% or below and critical at 20% or below."
| Control | What it does |
|---------|--------------|
| **Search name, serial, room, zone or notes** | Shows only the buttons that match what you type. |
| **Button guide** | Opens the guide to using the buttons. Show it to everyone who might use one. |
| **Add button** | Adds a button. See [Add a button](#add-a-button). |
| **Status**, **Battery**, **Room**, **Placement**, **Links** | Filter the list. |
| **Columns** | Chooses which columns show. |
| **Locate** | Helps you find the button in that row. |
| Column | What it shows |
|--------|---------------|
| **BUTTON** | The button's label, with its serial number underneath. |
| **STATUS** | For example **Online**, **Offline**, **Battery critical**, **Battery depleted** or **Keeps going offline**, plus **Test mode** for a button still in [test mode](https://docs.performancehub.co/module-docs/duress/setup#test-mode) and the button's setup if it's a silent one. See [Button setups](#button-setups) and [Keeps going offline](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#keeps-going-offline). |
| **ROOM** | Where the button is, as set on its Settings tab. |
| **ZONE** | The zone number your monitoring company uses for this button, as set on its Settings tab. |
| **BATTERY** | The battery level, marked OK, low or critical. |
| **SIGNAL** | How well a Display Screen Player or IoT Gateway hears the button. |
| **LAST SEEN** | When the button last reported. |
| **LAST TESTED** | When the button was last pressed in a test. |
An offline button shows **No reading** for its battery and signal, because its last reading may be out of date.
### Battery depleted
An offline button whose battery was almost empty when it was last heard shows **Battery depleted** instead, with its last reading greyed out. It can't send alarms until it's charged.

*Figure 4: A button that is offline with a depleted battery*
Its **Overview** tab says: "The battery is depleted: it was almost empty when this button was last heard, and it can’t send alarms until it’s charged. Charge it, then check it comes back online."

*Figure 5: A button's details when its battery is depleted*
Charge the button, check it shows **Online** again, then press it in a [walk test](https://docs.performancehub.co/module-docs/duress/testing-and-reports#run-a-test). Until then, make sure staff near it have another way to call for help.
### Button setups
Each button has a setup. The list shows it next to the status for the silent setups. Whatever the setup, a press raises the same alarm: the response plan, alarm destinations and response actions all run.
| Setup | What it's for |
|-------|---------------|
| Standard | Most places. |
| **Silent alarm** | Places where a sound at the button could escalate the risk, such as a consult room, a pharmacy counter or a hotel night desk. |
| **Silent alarm, no light** | Places where a sound or a light at the button could escalate the risk, such as a bar service point, a retail counter or a button carried by someone working alone with the public. |
When you place or move a button, think about who could see or hear it when it's pressed, and check its setup still suits the spot. The documentation deliberately leaves out how each setup behaves at the button. Staff are shown the button guide in the Duress module.
## A button's details
Click a button in the list to open its details. They have four tabs:
| Tab | What it holds |
|-----|---------------|
| **Overview** | **Quick actions**, the button's connection, device details and device alerts. **Locate now** helps you find the button; see [Locate a button](#locate-a-button). **Mute alerts** stops this button's offline, back-online and battery alerts; see [A button's device alerts](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#a-buttons-device-alerts). Alarms are always sent. |
| **Settings** | The button's label, room, zone, placement and notes, and its links to cameras, doors and screens. |
| **Button guide** | The guide to using the button, for staff. |
| **Advanced** | **Move to another facility**, **Remove from this facility** and **Open in Device Management**. Moving and removing need Device Management. |
Moving or removing a facility's last button warns you that Duress goes to sleep there, and that its contacts and monitoring centre are emailed.
A button that is still in test mode shows **Test mode** and **Go live now** at the top of its details. See [Test mode](https://docs.performancehub.co/module-docs/duress/setup#test-mode).
### Locate a button

*Figure 6: Locate now makes the button beep, and shows which Display Screen Player or IoT Gateway hears it*
Use **Locate now** to find a button that has been moved, or a carried button that has been put down somewhere. It's in **Quick actions** on the button's **Overview** tab: "The button beeps for 30 seconds so you can find it".
Once you click it, the quick action shows **Beeping now**, with the seconds left. "Listen for it near" names the Display Screen Player or IoT Gateway that hears the button, so you know where to start looking.
The beep can be heard by anyone nearby, so locate a button only when that's safe, for example outside opening hours or when the area is clear.
### Settings

*Figure 7: A button’s details: its label, room, zone, placement and notes*
| Field | What it's for |
|-------|---------------|
| **Label** | "Shown in alarms so responders know which button was pressed." |
| **Room** | "Shown in alarms so responders know where the button was pressed." |
| **Zone** | "The zone number your monitoring company uses for this button." |
| **Placement** | "Whether the button stays in one place or goes with the person wearing it." A fixed button is mounted on a wall or desk; a carried button goes with the person. |
| **Notes** | "For your team, e.g. where it is mounted or who carries it." |
"Labels, zones and the cameras, doors and screens a button is linked to need Duress Configuration at this facility."
## Links: cameras, doors and screens
"Give it a label and zone, and link its cameras, doors and screens." A button's links decide what happens at that button when it's pressed, together with the facility's [response actions](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#response-actions):
| Link | What it's used for |
|------|--------------------|
| Cameras | Footage around the alarm, and live view on the alarm page, from [CCTV / VSaaS](https://docs.performancehub.co/module-docs/cctv-vsaas/overview) |
| Doors | Doors that can unlock when someone acknowledges the alarm, from [Door & Gate Access](https://docs.performancehub.co/module-docs/door-gate-access/overview) |
| Screens | Display screens that show a staff screen alert |
> **Staff-only screens.** Link a button only to staff-only screens, such as a back office or a staff room. Never link screens that the public, or the person causing the threat, can see.
## What raises an alarm
| Trigger | What happens |
|---------|--------------|
| A button press | A real alarm. The response plan, alarm destinations and response actions all run. |
| A press during a test | A test, labelled TEST. Presses during a test are treated as tests, so book tests ahead and tell staff. See [Testing & reports](https://docs.performancehub.co/module-docs/duress/testing-and-reports). |
## Door & Gate Access
With [Door & Gate Access](https://docs.performancehub.co/module-docs/door-gate-access/overview) at the facility, Duress response actions can unlock a button's linked doors when someone acknowledges the alarm, and lock the doors down while the alarm is active. Responders can also unlock doors and lift a lockdown from the incident. Lockdown and door controls need Door & Gate Access as well as Duress. Test alarms never unlock doors, lock down or alert screens.
## Display Screens
[Display Screens](https://docs.performancehub.co/module-docs/display-screens/overview) works with Duress in three ways:
- **Duress alarm monitoring** lets a Display Screen Player receive presses from buttons within about 10 to 15 metres. See [How presses reach Performance Hub](#how-presses-reach-performance-hub).
- **Alert staff screens** shows an alert on the screens linked to the pressed button. Link only staff-only screens.
- A screen's [relay controller](https://docs.performancehub.co/module-docs/display-screens/screen-settings#relay-controller) can be set to **Duress alarm** or **Duress low battery**, so the relay responds to duress alarms.
## Related pages
- [Adding devices](https://docs.performancehub.co/module-docs/device-management/adding-devices): the Add Device dialog in Device Management
- [Screen settings](https://docs.performancehub.co/module-docs/display-screens/screen-settings#connectivity--peripherals): turn on Duress alarm monitoring for a Display Screen Player
- [Alerts & notifications](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications): who is alerted, and the response actions
- [Common questions](https://docs.performancehub.co/module-docs/duress/common-questions): No reading, Battery depleted, presses that don't alert and more
Source: https://docs.performancehub.co/module-docs/duress/devices-and-triggers
---
# Alerts and notifications
Duress settings decide who is told when a duress button is pressed, how they're told and what Performance Hub does automatically. This page covers each section of the Duress settings, and what your AI Agent can do with duress alarms.
## Quick reference
**Where to find it:** Side menu → **Duress** → Settings (cog icon) → **Response plan**, **Response actions**, **Device alerts**, **Integrations**, **Delivery log** or **Browser Notifications**
**Who can access:** Users with Duress Configuration at the facility can change these settings. **Browser Notifications** is for people who can respond to alarms. See [Roles & permissions](https://docs.performancehub.co/module-docs/duress/roles-and-permissions).
**Purpose:** Make sure every alarm reaches the right people, by the right channels, and that you hear when a message doesn't get through
## Response plan
**Settings** → **Response plan**: "Who is told when a duress button is pressed, and how long to wait before telling the next people."

*Figure 1: A response plan: who is alerted first, how, and who comes next*
A response plan is a list of tiers. You decide who is in each tier, how they're contacted and when each tier starts.
- **Tiers.** Tier 1 is the first group of people alerted. Each later tier is alerted, after the wait you choose, if nobody has acknowledged the alarm yet. Escalation stops as soon as someone acknowledges.
- **Contacts.** A contact can be a role ("Everyone holding this role at the facility is contacted, as the role changes."), a Performance Hub user, an email address or a phone number.
- **Channels.** Each contact is reached by **Push notification**, **SMS**, **Voice call** or **Email**, or a mix. You choose whether each channel is used at once or after a delay.
- **Reminders.** You can have reminders repeat until someone acknowledges. Reminders only go to people already alerted, by SMS, voice call and push notification, never by email or to monitoring centres.
The tiers, contacts and timings in the figures are one facility's choices. Agree your own with whoever is responsible for safety at your site.
### Brand response plans
A facility can follow its brand's response plan, add people to it, or have its own plan:
| The facility | What you see | What you can do |
|--------------|--------------|-----------------|
| Follows the brand's plan | The brand's plan | **Add people for this facility** or **Create own plan** |
| Adds to the brand's plan | "This facility follows its brand's response plan, plus the people added here." | **Edit extra people**, **Use own plan instead** or **Revert to template** |
| Has its own plan | The facility's plan | Edit the plan |
The brand's plan is set in Organisation Settings. See [Brand access & security](https://docs.performancehub.co/module-docs/organisation-settings/brand-access-security#duress).
### Who gets contacted

*Figure 2: The people the plan reaches today, with roles expanded*
**Who gets contacted** shows "The people this plan reaches today, with roles expanded to whoever holds them here. It updates as people and roles change." Check it whenever staff join or leave, or roles change. It warns you about gaps, such as:
- "No mobile number on their profile, so SMS and calls can’t reach them."
- "Nobody is contacted until a plan is set."
- A role in the plan that nobody at the facility holds.
## Response actions
**Settings** → **Response actions**: "What Performance Hub does automatically when a duress button is pressed. Test alarms never unlock doors, lock down or alert screens."
| Action | What it does |
|--------|--------------|
| **Alert staff screens** | Shows an alert on the display screens linked to the pressed button. Link only staff-only screens, never screens the public can see. |
| **Lock down the doors** | Locks the facility's doors down while the alarm is active, including doors that unlock on a schedule. Needs [Door & Gate Access](https://docs.performancehub.co/module-docs/door-gate-access/overview). |
| **Unlock doors when acknowledged** | Unlocks the doors linked to the pressed button when someone acknowledges the alarm, so responders can get in. **Doors for buttons with none linked**: "Unlocked when the pressed button has no doors of its own. Leave empty to unlock only linked doors." Needs Door & Gate Access. |
| **Keep footage around the alarm** | Keeps recordings from before the press until after the alarm ends, so [CCTV / VSaaS](https://docs.performancehub.co/module-docs/cctv-vsaas/overview) retention doesn't delete them, until someone releases them. **Cameras** chooses **Every camera**, **The pressed button's cameras** or **Only these**. Never used for test alarms. |
| **Show cameras on the alarm dashboard** | "Anyone with the alarm’s Manage incident link can watch the pressed button’s cameras live, and open recordings for 24 hours. Without linked cameras they see the facility’s." That includes view-only group links, so weigh who receives your alarm messages before you turn it on. See [Cameras](https://docs.performancehub.co/module-docs/duress/alarm-page#cameras). |
| **Allow door controls on the alarm dashboard** | Lets response plan contacts unlock doors, lock the facility down and lift a lockdown from their own alarm page link, without logging in. Group links are view only. Never for test alarms. Needs Door & Gate Access. See [Doors](https://docs.performancehub.co/module-docs/duress/alarm-page#doors). |
| **Contacts can resolve from the alarm page** | Lets response plan contacts resolve the alarm from their own alarm page link. See [Resolve](https://docs.performancehub.co/module-docs/duress/alarm-page#resolve). |
| **Text updates to everyone texted so far** | Sends updates posted for everyone alerted by SMS to everyone already texted about the alarm. Update texts are spaced out for each person, the person who posted the update isn't texted, and updates still show in the app and on the alarm page. Never for test alarms. |
| **Draft an AI summary** | Drafts a summary of the incident once it's resolved, for a responder to check and add as a note. "No names or contact details are sent." |
| **Workflows can acknowledge, resolve and raise alarms** | Lets workflows, and your AI Agent acting on its own, acknowledge, resolve and raise alarms and start tests; everything they do appears on the incident's timeline. |
### Brand templates
Response actions can come from the brand's template. The section then says: "These come from the template. Saving changes sets them for this facility only; its contacts still follow the template."
A facility that has saved its own shows "This facility sets its own response actions instead of the template's." **Use the template's** goes back to the brand's response actions.
## Alarm page links
"Messages carry one Manage incident link. Response plan contacts get their own link and can act from it." **Alarm page link** chooses where it goes:
- **Shared alarm page (default)**: "Anyone who gets the message sees everything a texted contact sees, and logs in to Performance Hub to act." Response plan contacts open the [alarm page](https://docs.performancehub.co/module-docs/duress/alarm-page) as themselves and can act from it, as far as the response actions allow. A link sent to a team chat or shared inbox is view only.
- **Log-in only**: "The message links to the alarm in Performance Hub, which needs a log-in."
A link works until 24 hours after the last alarm message sent to that person. See [The alarm page](https://docs.performancehub.co/module-docs/duress/alarm-page) for what people see when they open it.
## Alarm destinations
**Settings** → **Integrations** → **Alarm destinations**: "Everywhere this facility’s duress alarms are sent: the monitoring centre and your own places, such as a team chat, an on-call service or your own receiver."

*Figure 3: Alarm destinations: a team chat, an on-call service and an inbox*
Click **Add destination** to send alarms to email, SMS, a voice call, **Slack**, **Microsoft Teams**, **Google Chat**, **PagerDuty**, **Opsgenie** or a **Webhook**.
| Column | What it shows |
|--------|---------------|
| **DESTINATION** | The destination's name and type. |
| **APPLIES TO** | Where it was set: "This facility", "Brand: …" or "Organisation: …". A destination set for a brand or the whole organisation applies to every facility under it. |
| **SENDS** | Which events it receives. |
| **STATUS** | Whether it's sending. |
Each row has **Test**, **Edit** and **Delete**. Test a destination after you add or change it.
### Monitoring centre
Your monitoring centre is connected with your monitoring provider and Support, and is set up and tested in External Integrations (**Open in External Integrations**). Its status is **Not live yet**, **Live**, **Failing** or **Suspended**. Older sites may still have an **Alarm panel output (legacy)** card; ask Support about it.
### Staged monitoring change

*Figure 4: A monitoring centre connection that is live, with a change staged*
When a change to the monitoring centre connection is waiting to be tested, its row keeps its current status and adds: "A change is staged. Alarms use the current settings until it is tested and activated in External Integrations."
Nothing changes for your alarms until then: they keep going to the monitoring centre exactly as before. Whoever looks after the connection finishes the change with **Open in External Integrations**. If you didn't expect a change, ask them about it before it's activated.
## Device alerts
**Settings** → **Device alerts**: "Who is told about low button batteries, buttons going offline, buttons that can’t be heard, the monitoring centre connection and alarm messages that fail to send. Alarms themselves follow the response plan."

*Figure 5: Device alerts tell people about low batteries, offline buttons and failed messages*
Click **Add device alert**, choose what it's about and who is told, by push notification, SMS or email. If there's no battery rule, the page warns you that nobody is told about low batteries.
**Coverage lost** / **Coverage restored**: "When no display screen or IoT Gateway that listens for buttons is online, and when one is back." This is the alert that tells you the facility has nothing online to receive button presses, so add it for whoever looks after the facility's Display Screen Players or IoT Gateway. While coverage is lost, presses aren't received; make sure staff have another way to call for help until it's restored.
### A button's device alerts
Each button's **Overview** tab has a **Device alerts** section. It shows whether the button's alerts are on: "Offline, back online and battery alerts are on. Mute them while you fix a known problem; alarms are always sent."
### Keeps going offline

*Figure 6: A button that keeps going offline, with the Display Screen Player that hears it*
A button that drops offline and comes back again and again shows **Keeps going offline** in the button list and on Home. Rather than send an alert every time, Duress holds them: "This button keeps going offline and coming back, so its offline and back-online alerts are held until it stays online for an hour." The section shows when this started.
"That usually means a network, display screen or IoT Gateway problem rather than the button." The section lists what hears the button. To fix it:
1. Check that each Display Screen Player or IoT Gateway listed is powered on, online and on a steady network connection. A Display Screen Player that restarts or loses its network takes the button offline with it.
2. Check that **Duress alarm monitoring** is still on for the Display Screen Player, in its [Connectivity & peripherals settings](https://docs.performancehub.co/module-docs/display-screens/screen-settings#connectivity--peripherals).
3. If the button is more than about 10 to 15 metres from the nearest Display Screen Player, or outside the IoT Gateway's coverage, or something now blocks it, move the button closer, or add another Display Screen Player or IoT Gateway nearby, so more than one hears it.
4. Check the button's battery.
5. Once the button stays online, run a [walk test](https://docs.performancehub.co/module-docs/duress/testing-and-reports#run-a-test) to confirm presses get through.
While a button keeps going offline, a press may not get through. Make sure staff near it know another way to call for help until it's fixed.
### Mute alerts

*Figure 7: Mute a button's device alerts while you fix a known problem*
**Mute alerts** stops one button's device alerts for a while, for example while a Display Screen Player is replaced: "Offline, back online and battery alerts for" the button "won't be sent until the time runs out. Alarms are always sent."
1. On the button's **Overview** tab, click **Mute alerts**.
2. Choose how long in **Mute for**.
3. Optionally, add a **Reason**: "Shown to others who open this button."
4. Click **Mute**.
The alerts start again when the time runs out. Muted buttons are counted under **Alerts muted** on Home.
## Delivery log
**Settings** → **Delivery log**: "Every attempt to send this facility’s alarms, tests and device alerts, newest first. Select a row for its details."

*Figure 8: The delivery log lists every message sent for alarms, tests and device alerts*
Search the log, or filter it by **Result**, **Event**, **Channel**, **Destination** and **Range**. **Columns** chooses the columns, and **Export CSV** downloads the rows. Each row shows the event, the incident or test it belongs to, the destination and the result, such as **Sent** or **No delivery report**.
When alerts from the latest alarm or test didn't all get through, a red banner says so on every Duress tab. **Review** opens the failures; once you've fixed the cause, click **Mark as fixed**.
## Browser notifications
**Settings** → **Browser Notifications**: "Notifications on this computer". This section only shows for people who can respond to alarms.

*Figure 9: Turn on alarm notifications on each computer that monitors alarms*
Notifications are per computer and per browser: "These settings only apply to this browser." Until they're allowed, "duress alarms only show while Performance Hub is open, with no sound." The alarm sound comes with the notification.
1. On the computer that watches for alarms, open **Browser Notifications** and click **Turn on**.
2. Allow notifications when the browser asks.
3. Click **Send test notification** to check it works.
Turn notifications on at every computer that watches for alarms, such as a security office, a manager's office or a staff room. The notification plays the alarm, so choose computers that the person causing the threat can't see or hear. If the browser has blocked notifications, allow them for Performance Hub in the browser's site settings, then try again. **Turn off** stops them on that computer.
## Your AI Agent
Your [AI Agent](https://docs.performancehub.co/module-docs/ai-agent/overview) can list and read incidents, report on them, check buttons and help you locate one, add notes and updates, rename incidents and keep footage. Before it acknowledges or resolves an alarm, starts a test or starts a lockdown, it always asks you to confirm in the chat. It follows the same facility access as you. When it acts on its own, it can only acknowledge, resolve or raise alarms and start tests where **Workflows can acknowledge, resolve and raise alarms** is on.
## Related pages
- [Responding to alarms](https://docs.performancehub.co/module-docs/duress/responding-to-alarms): acknowledge, resolve and coordinate
- [The alarm page](https://docs.performancehub.co/module-docs/duress/alarm-page): what people see when they open the Manage incident link
- [Buttons & triggers](https://docs.performancehub.co/module-docs/duress/devices-and-triggers): a button's links to cameras, doors and screens
- [Brand access & security](https://docs.performancehub.co/module-docs/organisation-settings/brand-access-security#duress): a brand's duress response plans
- [Roles & permissions](https://docs.performancehub.co/module-docs/duress/roles-and-permissions): who can change these settings
Source: https://docs.performancehub.co/module-docs/duress/alerts-and-notifications
---
# Responding to alarms
When a duress button is pressed, the people in the response plan are alerted and the alarm appears in Performance Hub. This page is for the people who respond: acknowledging and resolving an alarm, coordinating on the incident, and reviewing past incidents.
## Quick reference
**Where to find it:** Side menu → **Duress** → **Home** (active alarms) or **Incidents**; or the **Manage incident** link in an alarm message
**Who can access:** Anyone with Duress at the facility. Response plan contacts can also act from their alarm page link, if the facility allows it.
**Purpose:** Respond to an alarm quickly, keep everyone informed and record what happened
## When an alarm comes in
An alarm reaches you in the ways your facility has set up:
- By the channels in the [response plan](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#response-plan): push notification, SMS, voice call or email, with a **Manage incident** link.
- As a browser notification, on computers where [browser notifications](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#browser-notifications) are on.
- On the Duress **Home** tab, as a card with the button and the time, and **Acknowledge**, **Resolve**, **Open incident** and **Post an update**.
- In Performance Hub's alarm banner, when the alarm is at another of your facilities.
Everyone with Duress at the facility also gets a push notification, in the browser and the Performance Hub app, and an entry in the notification bell. These open the alarm in Performance Hub, which needs a log-in. They're quietly replaced once someone acknowledges or resolves the alarm, and a notification about an update opens the incident's updates.
The **Manage incident** link in a text or email opens the [alarm page](https://docs.performancehub.co/module-docs/duress/alarm-page), where response plan contacts can follow and act on the alarm from their phone without logging in.
## Acknowledge an alarm
Click **Acknowledge** as soon as you're responding. Acknowledging tells everyone that you're responding, and stops the alarm escalating to the next people in the response plan. If the facility uses **Unlock doors when acknowledged**, the doors linked to the pressed button unlock so responders can get in.
## Resolve an alarm
When the situation is over:
1. Click **Resolve**. **Resolve alarm** opens.
2. Choose the **Outcome**: **Genuine emergency**, **False alarm**, **Test** or **Unknown**.
3. Add a **Note**: "Recorded on the incident timeline. Required for a genuine emergency."
4. Confirm.
Resolving a real alarm as **Test** records it as an unplanned test. It's left out of real-alarm reports and counts as a completed drill.
## The Incidents tab

*Figure 1: The incident history, with each alarm’s outcome and who acknowledged it*
**Incidents** lists every alarm and test at the facility, newest first. Filter it by **Status** and **Range**, which starts at **Last 12 months**.
| Column | What it shows |
|--------|---------------|
| **RAISED** | When the alarm was raised. |
| **ALARM** | The button that was pressed, with its zone. |
| **STATUS** | Where the incident is up to. Tests also show **TEST**. |
| **OUTCOME** | The outcome chosen when it was resolved. |
| **ESCALATION** | How far through the response plan the alarm went. |
| **ACKNOWLEDGED BY** | Who acknowledged it, and how, for example "via Duress console" or "via Alarm dashboard". |
Click a row to open the incident.
## An incident
An incident's page has an **All incidents** link back to the list, the incident's name, its status and a **TEST** pill for tests. Click the pencil to give it a name that's easy to find later. Names stay in the Duress console: they never reach the alarm page or messages.
The page has three tabs, **Activity**, **Evidence** and **Deliveries**, and a side panel with the alarm, the response, the buttons pressed, doors and footage.
### Activity

*Figure 2: An incident’s activity: updates for everyone alerted and staff-only notes*
**Activity** is the incident's timeline. Filter it with **All**, **Updates**, **Staff notes** and **System**.
To post, choose who sees it, then write your message:
| Option | Who sees it |
|--------|-------------|
| **Everyone alerted** | "Everyone alerted about this alarm sees updates, in the app and on their alarm page, and by text when update texts are on." |
| **Staff only note** | "Only people with Duress access in Performance Hub see staff notes." |
Updates can include text, photos, files and a location. Use an update for what everyone responding needs to know, such as "Security is at the front desk now.", and a staff note for anything that should stay inside Performance Hub. Updates close 24 hours after the alarm is resolved; staff notes stay open.
### Evidence and footage
**Evidence** shows footage from the cameras linked to the pressed button. **Open in CCTV** opens it in [CCTV / VSaaS](https://docs.performancehub.co/module-docs/cctv-vsaas/overview). When the facility uses **Keep footage around the alarm**, the kept footage is listed here and protected from CCTV retention: "Kept until someone with Duress Configuration releases it." **Release kept footage**, which needs Duress Configuration, lets it age out normally again. See [Retain & Archive](https://docs.performancehub.co/module-docs/cctv-vsaas/retain-and-archive) for keeping other recordings.
### Deliveries
**Deliveries** lists every message sent for this incident and its result. For every message the facility has sent, see the [delivery log](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#delivery-log).
### Doors
The side panel's **Doors** lists the doors linked to the alarm. **Unlock** unlocks one door and **Unlock all** unlocks them all. If the doors are locked down, it shows the lockdown and **Lift lockdown**. Each asks you to confirm first. Door controls need Duress and [Door & Gate Access](https://docs.performancehub.co/module-docs/door-gate-access/overview) at the facility.
Where the facility allows it, response plan contacts can also unlock doors, lock the facility down and lift a lockdown from their own link. See [Doors](https://docs.performancehub.co/module-docs/duress/alarm-page#doors) on the alarm page.
### AI summary
When the facility uses **Draft an AI summary**, a draft summary of the incident appears once it's resolved. Check it, correct anything that's wrong, then click **Add as note** to add it to the timeline.
## Related pages
- [The alarm page](https://docs.performancehub.co/module-docs/duress/alarm-page): what response plan contacts and group chats see from the Manage incident link
- [Alerts & notifications](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications): the response plan, response actions and browser notifications
- [Testing & reports](https://docs.performancehub.co/module-docs/duress/testing-and-reports): tests and the reports built from incidents
- [Roles & permissions](https://docs.performancehub.co/module-docs/duress/roles-and-permissions): who can respond
- [Common questions](https://docs.performancehub.co/module-docs/duress/common-questions): quick answers about alarms and incidents
Source: https://docs.performancehub.co/module-docs/duress/responding-to-alarms
---
# The alarm page
Every duress alarm message carries a **Manage incident** link. It opens the alarm page: a single page, made for a phone, where the people alerted can see what's happening and, depending on the facility's settings, acknowledge the alarm, post updates, unlock doors and resolve it. This page is for anyone who opens that link, such as a practice manager at a clinic, a duty manager at a hotel, a teacher on call or a store manager, and for the people who decide what the alarm page shows.
## Quick reference
**Where to find it:** The **Manage incident** link in an alarm text, email or chat message
**Who can access:** Anyone who opens the link. Response plan contacts can act from their own link without logging in. People who get the message in a group chat or shared inbox see the alarm, and log in to Performance Hub to act.
**Purpose:** See an alarm at a glance from your phone, say you're responding, and coordinate until it's resolved
## Who gets a link
"Messages carry one Manage incident link. Response plan contacts get their own link and can act from it." Nobody needs to log in to open it.
Where the link goes depends on the facility's **Alarm page link** setting. See [Alarm page links](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#alarm-page-links).
| Who | What the link does |
|-----|--------------------|
| A response plan contact alerted by SMS or email | Opens the alarm page as that person. They can act from it, as far as the facility allows. |
| A group chat, team channel or shared inbox, with **Shared alarm page (default)** | "Anyone who gets the message sees everything a texted contact sees, and logs in to Performance Hub to act." See [Group links are view only](#group-links-are-view-only). |
| Anyone, with **Log-in only** | "The message links to the alarm in Performance Hub, which needs a log-in." |
Performance Hub users with Duress at the facility also get a push notification and an entry in the notification bell. Those open the alarm in Performance Hub, not the alarm page. See [When an alarm comes in](https://docs.performancehub.co/module-docs/duress/responding-to-alarms#when-an-alarm-comes-in).
### How long a link works
A link works until 24 hours after the last alarm message sent to that person. The bottom of the page shows the time: "This link works until" a date and time. After that, the link opens a notice instead. See [This link has expired](#this-link-has-expired).
### Keep your link to yourself
Alarm pages aren't indexed by search engines. Your own link lets anyone who has it act as you, so don't forward it. To bring someone else in, ask whoever looks after Duress to add them to the [response plan](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#response-plan).
## What's on the page

*Figure 1: The alarm page for an open alarm, on a phone*
The page keeps itself up to date while it's open, so you don't need to refresh it. Its sections only show when they apply, so what you see depends on the alarm and the facility's settings.
| Section | What it's for |
|---------|---------------|
| Status | The facility, the button and its room, when it was pressed and where the alarm is up to. See [Status](#status). |
| **Acknowledge: I’m responding** | Tell everyone you're responding. See [Acknowledge](#acknowledge). |
| **Resolve alarm** | Close the alarm with an outcome, where the facility allows it. See [Resolve](#resolve). |
| **Updates** | Share what's happening with everyone responding. See [Updates](#updates). |
| **Doors** | Unlock doors or lock the facility down, where the facility allows it. See [Doors](#doors). |
| Cameras | Live view and recordings from the button's cameras, where the facility allows it. See [Cameras](#cameras). |
| **Contacts** | Call or email the others alerted, the facility and the organisation. See [Contacts](#contacts). |
| **What happened so far** | The alarm's timeline. See [What happened so far](#what-happened-so-far). |
On a desktop screen the same sections show in a single column.

*Figure 2: The same alarm on a desktop screen*
### Status
The top of the page names the facility, the button and its room, and when it was pressed, for example "Pressed 4 minutes ago". Under that, it shows where the alarm is up to:
| Status | What it says |
|--------|--------------|
| **Open** | "No one has acknowledged this alarm yet." |
| **Acknowledged** | "Acknowledged by" the person responding, with the time. |
| **Resolved** | "Resolved by" the person who resolved it, the time and the outcome, for example **Genuine emergency**. |

*Figure 3: An acknowledged alarm, with an update from the person responding*

*Figure 4: A resolved alarm*
A test alarm is marked **TEST**: "This is a test alarm. No one needs help." Once it's resolved, the page says "This alarm was resolved as a test."

*Figure 5: A test alarm*
### Acknowledge
Tap **Acknowledge: I’m responding** as soon as you're responding. "Everyone alerted will see your name, and the alarm stops escalating." The page confirms: "Acknowledged. Everyone alerted can see you’re responding."
If someone else acknowledged it first, the page names them: "… acknowledged this alarm first." You can still post updates and use anything else the page offers.
### Resolve
**Resolve alarm** only shows when the facility turns on **Contacts can resolve from the alarm page**. See [Response actions](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#response-actions).

*Figure 6: Resolving an alarm from the alarm page*
1. Tap **Resolve alarm**.
2. Choose the **Outcome**: **Genuine emergency**, **False alarm**, **Test** or **Unknown**.
3. Add a note: "Recorded on the incident for the facility’s staff. Required for a genuine emergency."
4. Tap **Resolve**.
The page confirms: "Alarm resolved. Everyone alerted gets the all-clear."
### Updates
**Updates**: "Share what’s happening with everyone responding." Post text, or add a **Photo**, a **File** or **Share my location**, then tap **Send**. Updates are added to the incident in Performance Hub, where the facility's staff see them too. Use updates for what everyone responding needs to know, for example "Security is at the front desk now." or "Police have arrived."
Updates close 24 hours after the alarm is resolved: "Updates closed 24 hours after this alarm was resolved."

*Figure 7: Updates close 24 hours after the alarm is resolved*
On a group link, the card says "Log in to Performance Hub to post an update."
### Doors
**Doors** only shows when the facility turns on **Allow door controls on the alarm dashboard** and uses [Door & Gate Access](https://docs.performancehub.co/module-docs/door-gate-access/overview). It lists the doors linked to the pressed button. If no doors are linked to it, every door at the facility is listed instead: "No doors are linked to this button, so every door at" the facility "is listed."
| Control | What it does |
|---------|--------------|
| **Unlock** | Unlocks one door. |
| **Unlock all** | Unlocks every door listed. It shows when the alarm has more than one door and isn't listing every door at the facility. |
| **Lock down the facility** | Locks the facility down while you deal with the alarm. |
| **Lift lockdown** | Ends the lockdown. |
Each one asks you to confirm first. To unlock: "Unlock this door?" "Each door opens for its normal hold time, then locks again. This is added to the timeline with your name."

*Figure 8: Confirming a door unlock*
To lock down: "Member access is blocked at every door of" the facility "until the lockdown is lifted. Doors that unlock on a schedule are locked too."
During a lockdown, the card says the doors are locked down, with **Lift lockdown**: "You can still unlock a door below, for example to let emergency services in."

*Figure 9: Doors during a lockdown*
The card explains when doors can't be used from the page:
- "Doors can’t be unlocked from a test alarm."
- "This alarm is resolved, so its doors can’t be unlocked from here."
- "Door/Gate Access isn’t turned on at this facility."
- "There are no doors at this facility."
On a group link, the doors are listed without any controls.
### Cameras
Cameras only show when the facility turns on **Show cameras on the alarm dashboard**. Each of the pressed button's cameras shows **Live** video, or still pictures where live video isn't available ("Still pictures, updated every … seconds."). **Open recordings** opens the camera's recordings.
Anyone with the alarm's **Manage incident** link can watch the cameras live and open recordings for 24 hours, and that includes group links. Facilities should weigh this before turning it on. See [Response actions](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#response-actions).
### Contacts
**Contacts** lists who else you can reach: **Also alerted** (the other people alerted about this alarm), the facility and the organisation, each with **Call** and **Email** buttons. Phone numbers and email addresses aren't displayed.

*Figure 10: Contacts, with Call and Email buttons*
### What happened so far
**What happened so far** is the alarm's timeline: when it was raised and acknowledged, updates, door actions and when it was resolved. On a phone, it's at the bottom of the page.
### The footer
The bottom of the page shows who the link was sent to, "Sent to" the person's name, and "This link works until" a date and time.
## Group links are view only
A link shared with a group chat, team channel or shared inbox doesn't belong to one person, so it's view only: "This link was shared with a group, so Performance Hub doesn’t know who you are. Log in to acknowledge, post updates, unlock doors or resolve this alarm." **Log in to Performance Hub** opens the alarm in Performance Hub.

*Figure 11: A view-only group link*
## If the page loses its connection
If the page can't reach Performance Hub, for example when your phone loses signal, it shows "Can’t reach Performance Hub. Retrying…" and keeps trying. What's on the page may be out of date until it reconnects.

*Figure 12: The page has lost its connection and is retrying*
## When a link doesn't open the alarm
### This link has expired
"This link has expired" "Alarm links stop working 24 hours after the last alarm message. Log in to Performance Hub to see this alarm."

*Figure 13: The link has expired*
### This link isn't valid
"This link isn’t valid" "Check that you opened the whole link from the alarm text, email or chat message." Open the link straight from the message.

*Figure 14: The link isn't valid*
### This alarm no longer exists
"This alarm no longer exists" "Log in to Performance Hub to see the facility’s alarms."

*Figure 15: The alarm no longer exists*
### Can't load the alarm
"Can’t load the alarm" "Check your connection and try again." Tap **Try again**.

*Figure 16: The alarm can't be loaded*
If you can't open the alarm page during a real emergency, don't wait: call the facility or emergency services, and log in to Performance Hub when you can.
## Theme and language
The alarm page follows your phone's light or dark theme and language. If you're logged in to Performance Hub, it uses your Performance Hub settings instead.
## What the facility controls
These [response actions](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#response-actions) decide what the alarm page offers. They need Duress Configuration at the facility.
| Setting | What it adds to the alarm page |
|---------|--------------------------------|
| **Alarm page link** | Whether messages link to the alarm page or to Performance Hub. See [Alarm page links](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#alarm-page-links). |
| **Contacts can resolve from the alarm page** | **Resolve alarm** for response plan contacts. |
| **Allow door controls on the alarm dashboard** | **Doors**, with unlocking, lockdown and lifting a lockdown for response plan contacts. Never for test alarms. |
| **Show cameras on the alarm dashboard** | Live view and recordings for anyone with the link, group links included. |
## Related pages
- [Responding to alarms](https://docs.performancehub.co/module-docs/duress/responding-to-alarms): acknowledging and resolving in Performance Hub
- [Alerts & notifications](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications): the response plan, response actions and alarm page links
- [Door & Gate Access](https://docs.performancehub.co/module-docs/door-gate-access/overview): the doors an alarm can unlock or lock down
- [CCTV / VSaaS](https://docs.performancehub.co/module-docs/cctv-vsaas/overview): the cameras linked to duress buttons
Source: https://docs.performancehub.co/module-docs/duress/alarm-page
---
# Testing and reports
A duress button is a safety device: it has to work the first time someone needs it. Test every button on a regular schedule, and again after you move a button, replace its battery or change the Display Screen Players or IoT Gateway near it. Reports then show how quickly alarms are answered and how ready the facility is.
> **Book tests ahead and tell staff.** Presses during a test are treated as tests, so everyone should know when a test is on.
## Quick reference
**Where to find it:** Side menu → **Duress** → **Testing** or **Reports**
**Who can access:** Users with Duress can start and end tests and see reports. Booking and editing test schedules needs Duress Configuration. See [Roles & permissions](https://docs.performancehub.co/module-docs/duress/roles-and-permissions).
**Purpose:** Prove every button works end to end, and measure how well alarms are answered
## Walk test or full response drill
| | **Walk test** | **Full response drill** |
|---|---|---|
| In the product | "Someone presses every button once. Only tier 1 of the response plan is told, with TEST-labelled messages, and the monitoring centre if it gets test codes." | "Someone presses a button, then the whole response plan runs, every tier, with TEST-labelled messages, until someone acknowledges. Measures how long that takes." |
| Checks | Every button reaches Performance Hub and alerts the first people in the plan | The whole response plan, and how long it takes someone to acknowledge |
| Use it | Regularly, and after any change to the buttons, Display Screen Players or IoT Gateway | When you want to rehearse the full response with everyone in the plan |
Test alarms never unlock doors, lock down or alert screens, whichever test you run.
## Run a test

*Figure 1: Run a walk test or a full response drill*
**Run a test** checks that every button at the facility works, end to end.
1. Open **Testing** and choose **Walk test** or **Full response drill**.
2. Choose the **Duration**: "Presses are tests until the time is up, then real alarms again. You can end the test sooner." Choose a duration that gives you time to reach every button.
3. Choose what goes to the **Monitoring centre**: **Send test codes** or **Send nothing**. Agree this with your monitoring provider.
4. Click **Start walk test** for a walk test, or **Start drill** for a full response drill.
5. Press every button for a walk test, or one button for a drill.
6. If you finish before the duration is up, click **End test** in the test panel, or **End early** on the test's row in the list.
Tests never run by themselves: someone has to press every button. The durations shown in the figures are examples, not recommendations.
### Test results
The results list every button, whether it was pressed or not, and who each press told. They also suggest next steps: a missed button opens **Devices**, and a failed message opens the [delivery log](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#delivery-log). Fix any failure straight away, and make sure staff know another way to call for help until the button works. See [A button was pressed but no alert came through](https://docs.performancehub.co/module-docs/duress/common-questions#a-button-was-pressed-but-no-alert-came-through).
## Test schedules

*Figure 2: The facility's own tests, and the brand and organisation tests it follows, moves or skips*
**Test schedules**: "Everyone involved is emailed when a test is booked, changed or cancelled, then 7 days, 1 day and 30 minutes before each test, and with the results afterwards."
1. Click **Schedule a test**.
2. Choose the test type, when it starts, its duration and what goes to the monitoring centre.
3. Choose how often it repeats: once, **Monthly**, **Quarterly**, **Twice a year** or **Yearly**.
4. Save. Everyone involved is emailed.
The list shows each schedule's **TEST**, **SET BY**, **STARTS**, **REPEATS**, **DURATION**, **MONITORING CENTRE**, **HERE** and **EMAIL**. Tick **Show cancelled schedules** to include cancelled ones.
- **Brand and organisation schedules** apply to several facilities at once: "You can give this facility its own time or skip one here without changing it anywhere else." Their **SET BY** column shows the brand or "Organisation". They're managed in Organisation Settings; see [Brand test schedules](#brand-test-schedules).
- **Cancel one test:** click **Cancel** on the test's row. The "Cancel this test?" dialog explains that only this test is cancelled, and later tests in the schedule still go ahead. **Cancel test** confirms, and **Keep test** closes the dialog without cancelling.
- **Cancel a schedule:** click **Cancel** on the schedule's row. In the "Cancel this schedule?" dialog, **Cancel schedule** cancels its upcoming tests and emails everyone who was told about them. Tests already running carry on. If the facility's schedule replaces a shared schedule, cancelling it also means the facility stops following the shared schedule.
A scheduled test still needs someone to press the buttons. Booking and editing schedules needs Duress Configuration. Organisation-wide schedules need an Organisation Administrator, or Duress Configuration across the whole organisation.
### Brand and organisation schedules at a facility
The **HERE** column shows what the facility does with each brand or organisation schedule:
| HERE | What it means | What you can do |
|------|---------------|-----------------|
| **Follows** | The facility runs the test at the shared time. | **Change for this facility** gives it its own time. **Skip here** skips it at this facility. |
| **Own time here** | The facility runs the test at its own time. | **Change for this facility** changes that time. **Follow again** goes back to the shared time. |
| **Skipped here** | The facility doesn't run this test. | **Change for this facility** gives it its own time. **Follow again** goes back to the shared time. |
None of these change the schedule at any other facility. If you skip a test, make sure the buttons are still tested by another test on a regular schedule.
## Brand test schedules

*Figure 3: A brand's test schedules, and the organisation schedules that cover it*
**Where to find it:** Settings (cog icon) → **Organisation Settings** → **Brands & Customisation** → a brand → **Duress** → **Test schedules**
**Brand test schedules**: "Book tests for every facility in a brand, the whole organisation or chosen facilities. Each facility can give a test its own time or skip it from its Testing tab."
Click **Schedule a test** to book one, the same way as at a facility. The **COVERS** column shows which facilities each schedule covers, and **AT EACH FACILITY** shows how many follow it, have their own time or skip it, for example "3 following", "1 own time" and "1 skipped". **Edit** and **Cancel** change or cancel the schedule everywhere it applies. Tick **Show cancelled schedules** to include cancelled ones.
**From the organisation** lists the schedules "Set for the whole organisation, so they cover this brand too." They're managed on the **Organisation Duress** page.
## Organisation Duress

*Figure 4: Duress buttons and tests across every facility in the organisation*
**Where to find it:** **Organisation-wide Duress** in a brand's **Duress** section, or **Organisation Duress** on a brand's **Test schedules** tab
**Organisation Duress** covers "Duress buttons across every brand, and test schedules for the whole organisation or several brands. Response plans are set per brand." It has two tabs:
- **Buttons** totals the buttons across the organisation: **BUTTONS**, **ONLINE**, **NEED ATTENTION** and **FACILITIES WITHOUT BUTTONS**. The **Facilities** table shows each facility's **BRAND**, **BUTTONS**, **STATUS**, **BATTERY**, **NEXT TEST** and **LAST COMPLETED TEST**. "Battery is shown for online buttons only." Filter it by **Brand**, and click **Open** to see a facility's buttons in Duress.
- **Test schedules** books and manages tests for the whole organisation or several brands.
Use it to spot facilities with no buttons yet, buttons that need attention, and facilities whose next test isn't booked.
## Reports

*Figure 5: Alarm response figures for the last 12 months*
**Reports** covers the selected facility over the range you choose. **Download CSV** and **Download PDF** export it.
| Group | What it measures |
|-------|------------------|
| **Alarm response** | Time to acknowledge, time to resolve, the false-alarm rate and the number of real alarms |
| **Readiness** | Test coverage, buttons tested, device uptime and delivery success |
| **Month by month** | How the figures change from one month to the next |
| Button health | How each button at the facility is doing |
| Test history | The tests run at the facility and their results |
**P50** is the median: half of the alarms were quicker than this. **P95**: 95% of alarms were acknowledged within the P95 time. Tests aren't counted as real alarms. A real alarm resolved as a test counts as an unplanned test, which counts as a completed drill.
## Related pages
- [Setting up Duress](https://docs.performancehub.co/module-docs/duress/setup#prepare-your-staff): preparing staff for tests
- [Responding to alarms](https://docs.performancehub.co/module-docs/duress/responding-to-alarms): acknowledge and resolve
- [Alerts & notifications](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications): the response plan a test checks
- [Common questions](https://docs.performancehub.co/module-docs/duress/common-questions): troubleshooting buttons that don't alert
Source: https://docs.performancehub.co/module-docs/duress/testing-and-reports
---
# Roles and permissions
Duress separates responding to alarms from configuring them, so front-line staff can respond without being able to change who is alerted. Access is given through roles, like every other Performance Hub module.
## Quick reference
**Where to find it:** Settings (cog icon) → **Identity and Access Management** → **Roles**
**Who can access:** Organisation admins, and people whose role includes Identity and Access Management
**Purpose:** Give responders and admins the Duress access they need, and no more
## The two modules
| Module | What it allows |
|--------|----------------|
| **Duress** | Respond to alarms: **Home**, **Incidents** and **Reports**, acknowledging and resolving alarms, posting updates and staff notes, and starting and ending tests |
| **Duress Configuration** | Configure Duress: the response plan, response actions, device alerts, integrations, button labels and links, and test schedules |
**Devices**, **Testing** and the settings open with either module, but changing them needs Duress Configuration.
In module pickers, such as when you create or edit a role, Duress Configuration sits under Duress:
- Ticking **Duress Configuration** also ticks **Duress**.
- **Duress** can't be unticked while **Duress Configuration** is ticked. Untick Duress Configuration first.
- Both need Device Management, so adding either to a role adds Device Management too.
## What each task needs
| Task | Needs |
|------|-------|
| Acknowledge, resolve and post on an alarm | Duress |
| Start or end a test | Duress |
| Change the response plan, response actions or device alerts | Duress Configuration |
| Change a button's label, room, zone or links | Duress Configuration |
| Book or edit test schedules | Duress Configuration |
| Release kept footage | Duress Configuration |
| Add, move or remove a button | Device Management |
| Unlock doors or lift a lockdown from an incident | Duress and Door & Gate Access |
| Act from an alarm's [alarm page](https://docs.performancehub.co/module-docs/duress/alarm-page) link | Being a response plan contact; no Performance Hub module. What they can do depends on the facility's response actions. |
| See the facility's integrations | Duress Configuration or External Integrations |
| Set up the monitoring centre | External Integrations |
| Book organisation-wide test schedules | Organisation Administrator, or Duress Configuration across the whole organisation |
Adding, moving and removing buttons also needs Device Management. Door controls also need Door & Gate Access.
## Role templates
These Performance Hub role templates include Duress:
| Role template | Duress | Duress Configuration | Note |
|---------------|--------|-----------------|------|
| **Organisation Administrator** | Yes | Yes | Has every module |
| **Site Owner** | Yes | Yes | Business Suite organisations only |
| **Site Manager** | Yes | Yes | |
| **Technology Administrator** | Yes | Yes | |
| **Front of House** | Yes | No | |
| **Security** | Yes | No | |
| **Instructor** | Yes | No | Business Suite organisations only |
| **Screens & Audio**, **Marketing**, **Finance**, **Viewer** | No | No | |
To give someone access a template doesn't include, create a role with the modules they need. See [Roles](https://docs.performancehub.co/module-docs/identity-access-management/roles) and [Role templates](https://docs.performancehub.co/module-docs/identity-access-management/role-templates).
## Related pages
- [Roles](https://docs.performancehub.co/module-docs/identity-access-management/roles): create and assign roles
- [Access scope & modules](https://docs.performancehub.co/module-docs/identity-access-management/access-scope-and-modules): which modules and facilities a role covers
- [Duress overview](https://docs.performancehub.co/module-docs/duress/overview): Duress and Duress Configuration at a glance
- [Alerts & notifications](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications): the settings Duress Configuration controls
Source: https://docs.performancehub.co/module-docs/duress/roles-and-permissions
---
# Common questions
Quick answers about Duress, and troubleshooting for a button press that didn't raise an alert.
## Quick reference
**Where to find it:** Side menu → **Duress**
**Who can access:** Users with Duress or Duress Configuration at the facility
**Purpose:** Answer common questions about buttons, alerts, tests and incidents
## Buttons
**Do duress buttons need anything else at the site?**
Yes. Each button needs a **Display Screen Player** or an **IoT Gateway** at the facility to receive its presses. Without one, presses aren't received. Either is enough, and more than one is better. See [How presses reach Performance Hub](https://docs.performancehub.co/module-docs/duress/devices-and-triggers#how-presses-reach-performance-hub).
**Why does a button show No reading?**
The button is offline, so its last battery and signal readings may be out of date. Check that a Display Screen Player or IoT Gateway near it is online. See [The button list](https://docs.performancehub.co/module-docs/duress/devices-and-triggers#the-button-list).
**Why does a button show Battery depleted?**
The button is offline, and its battery was almost empty when it was last heard. "It can’t send alarms until it’s charged." Charge it, then check it comes back online. See [Battery depleted](https://docs.performancehub.co/module-docs/duress/devices-and-triggers#battery-depleted).
**Why does the documentation leave out how the buttons work?**
Duress is silent by design, so the documentation deliberately leaves out how the buttons behave. Staff are shown the button guide in the Duress module. See [Prepare your staff](https://docs.performancehub.co/module-docs/duress/setup#prepare-your-staff).
**If I mute a button's alerts, are its alarms muted too?**
No. **Mute alerts** only stops the button's offline, back-online and battery alerts. Alarms are always sent. See [A button's details](https://docs.performancehub.co/module-docs/duress/devices-and-triggers#a-buttons-details).
**What does Keeps going offline mean?**
The button keeps dropping offline and coming back, usually because of a network, Display Screen Player or IoT Gateway problem rather than the button. Its offline and back-online alerts are held until it stays online for an hour. See [Keeps going offline](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#keeps-going-offline).
**Why does a button show Test mode?**
It was added with **Test it before it goes live**, and its test press hasn't arrived yet. The first press before the time shown is only a test. **Go live now** in its details ends test mode early. See [Test mode](https://docs.performancehub.co/module-docs/duress/setup#test-mode).
**What do Silent alarm and Silent alarm, no light mean?**
They're button setups for places where a sound, or a sound or a light, at the button could escalate the risk. A press still raises a full alarm. See [Button setups](https://docs.performancehub.co/module-docs/duress/devices-and-triggers#button-setups).
**What happens if I remove the last button at a facility?**
Duress goes to sleep at that facility, and its contacts and monitoring centre are emailed. You're warned before you confirm.
## Alerts and notifications
**Why didn't my computer show an alarm?**
Browser notifications are probably off on that computer or browser. Until they're on, duress alarms only show while Performance Hub is open, with no sound. Turn them on in **Settings** → **Browser Notifications** on each computer that watches for alarms. See [Browser notifications](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#browser-notifications).
**Why is nobody being alerted at a new facility?**
Duress is asleep until the facility's first button is added, and nobody is contacted until a response plan is set. See [Setting up Duress](https://docs.performancehub.co/module-docs/duress/setup).
**Why can't someone get SMS or voice calls?**
They have no mobile number on their Performance Hub profile. **Who gets contacted** warns you about this. See [Who gets contacted](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#who-gets-contacted).
**Who can change the response plan?**
People with Duress Configuration at the facility. A brand's plan is changed in Organisation Settings. See [Roles & permissions](https://docs.performancehub.co/module-docs/duress/roles-and-permissions).
**How do I know if an alarm message didn't get through?**
A red banner shows on every Duress tab when alerts from the latest alarm or test didn't all get through, and the [delivery log](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#delivery-log) shows every attempt and its result. A device alert can also tell people when an alarm message fails to send.
**Why does my alarm link say it has expired?**
"Alarm links stop working 24 hours after the last alarm message." Log in to Performance Hub to see the alarm. See [The alarm page](https://docs.performancehub.co/module-docs/duress/alarm-page#this-link-has-expired).
**Why can't I acknowledge from an alarm link shared in our team chat?**
A link shared with a group is view only, because Performance Hub doesn't know who opened it. Log in to Performance Hub to act. See [Group links are view only](https://docs.performancehub.co/module-docs/duress/alarm-page#group-links-are-view-only).
**Why does the monitoring centre say a change is staged?**
A change to the monitoring centre connection is waiting to be tested and activated in External Integrations. Until then, alarms keep using the current settings. See [Staged monitoring change](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#staged-monitoring-change).
## Tests and incidents
**Does a test alarm unlock doors?**
No. Test alarms never unlock doors, lock down or alert screens, and footage isn't kept for them. See [Response actions](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#response-actions).
**What happens if I resolve a real alarm as a test?**
It's recorded as an unplanned test, left out of real-alarm reports and counted as a completed drill. See [Resolve an alarm](https://docs.performancehub.co/module-docs/duress/responding-to-alarms#resolve-an-alarm).
**Who can see staff notes on an incident?**
"Only people with Duress access in Performance Hub see staff notes." Updates for **Everyone alerted** are also seen by the people alerted, on their alarm page. See [Activity](https://docs.performancehub.co/module-docs/duress/responding-to-alarms#activity).
**Do tests run by themselves?**
No. A scheduled test emails everyone involved, but someone still has to press every button. See [Test schedules](https://docs.performancehub.co/module-docs/duress/testing-and-reports#test-schedules).
**Can a facility move or skip a brand's test?**
Yes. On the facility's **Testing** tab, **Change for this facility** gives a brand or organisation test its own time there, and **Skip here** skips it, without changing it anywhere else. **Follow again** goes back to the shared time. See [Brand and organisation schedules at a facility](https://docs.performancehub.co/module-docs/duress/testing-and-reports#brand-and-organisation-schedules-at-a-facility).
## A button was pressed but no alert came through
**Symptom:** Someone pressed a duress button and nobody was alerted, a walk test press didn't arrive, or the button shows **Offline**, **Battery depleted** or **Keeps going offline**.
**Resolution:**
1. **Check a Display Screen Player or IoT Gateway near the button is online** in [Device Management](https://docs.performancehub.co/module-docs/device-management/overview). For a Display Screen Player, also check **Duress alarm monitoring** is on in its [Connectivity & peripherals settings](https://docs.performancehub.co/module-docs/display-screens/screen-settings#connectivity--peripherals). Without one online, presses aren't received. If a player is offline, see [A screen is offline](https://docs.performancehub.co/module-docs/display-screens/hardware-troubleshooting#a-screen-is-offline).
2. **Open the button** in Duress → **Devices**. Look for **Offline** or **Battery depleted**, and charge it if needed. "Not heard for over 16 minutes. Check the button is charged and within range of a display screen or IoT Gateway." A button that has never been heard says "No display screen or IoT Gateway has heard it in the last day."
3. **If the button is new,** allow up to 15 minutes: "It can take up to 15 minutes to come online — if it does not, restart the facility’s display screens or IoT Gateway." If it still doesn't come online, restart the facility's Display Screen Players or IoT Gateway.
4. **Check the button is within range:** about 10 to 15 metres of a Display Screen Player, or within the IoT Gateway's coverage. Move it closer, or add another Display Screen Player or IoT Gateway nearby. See [Placement](https://docs.performancehub.co/module-docs/duress/devices-and-triggers#placement).
5. **Check the response plan and the delivery log,** in case the alarm was raised but a message failed or nobody was in the plan. See [Who gets contacted](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#who-gets-contacted) and the [delivery log](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#delivery-log).
6. **Walk-test the button** once it's fixed. See [Run a test](https://docs.performancehub.co/module-docs/duress/testing-and-reports#run-a-test).
7. **Until it's fixed, make sure staff have another way to call for help.**
If the button shows **Keeps going offline**, also work through [Keeps going offline](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#keeps-going-offline). To hear about this sooner next time, add **Coverage lost** / **Coverage restored** and offline [device alerts](https://docs.performancehub.co/module-docs/duress/alerts-and-notifications#device-alerts).
## Related pages
- [Duress overview](https://docs.performancehub.co/module-docs/duress/overview): what Duress does and how an alarm works
- [Buttons & triggers](https://docs.performancehub.co/module-docs/duress/devices-and-triggers): Display Screen Players and IoT Gateways, adding buttons and the button list
- [The alarm page](https://docs.performancehub.co/module-docs/duress/alarm-page): what people see when they open an alarm's Manage incident link
- [Testing & reports](https://docs.performancehub.co/module-docs/duress/testing-and-reports): walk tests and test schedules
- [Device Management troubleshooting](https://docs.performancehub.co/module-docs/device-management/troubleshooting#a-duress-button-was-pressed-but-no-alert-came-through): the same checks from Device Management
Source: https://docs.performancehub.co/module-docs/duress/common-questions
---
# Model Router
**Model Router** gives your facility prepaid access to a catalog of leading AI models through a single, OpenAI-compatible endpoint. Your Performance Hub AI Agents connect to it automatically with no setup, Workflows can connect too, and you can provision your own API keys to plug the same models into third-party or in-house software — a booking assistant, a clinic intake form or a hotel concierge app. You top up a USD balance, set spend limits, and watch every request in real time — all from one place.
## Quick Reference
**Where to find it:** Main menu → **Model Router** (Technology Stack facilities). The module sits in the **AI Modules & Agents** group of the main menu.
**Who can access:** Organisation Administrators only (Technology Stack facilities).
**Purpose:** Buy AI inference on a prepaid balance: manage API keys, spend limits, auto top-up, and usage.
## How it fits together
- Your facility holds a **prepaid balance** in USD. You top it up with your stored billing card (manually or automatically), and every AI request draws down the balance at the price of the model that served it.
- The **model catalog** covers models from many providers. All models are enabled by default; you can turn individual models on or off for your facility, and restrict them further per API key or per connected service.
- There are **two ways to consume models**:
- **Connected Services** — paired AI Agent devices connect automatically, with no API key needed, and Workflows can connect as well.
- **Your own API keys** — provision keys and point any OpenAI-compatible SDK or tool at the Performance Hub endpoint.
- **Spend limits** (daily, monthly, and a lifetime hard cap) let you bound spend independently of the balance, across every key and connected service — with tighter per-key and per-service limits available on top.
- Every request is recorded — model, tokens, cost, latency, and status — in the **Activity** analytics and the **Logs** tab. Prompt and response content is never stored.
## The main areas
The module has four tabs plus a Settings area (cog icon, top right):
| Area | What you do there |
|------|-------------------|
| **Overview** | The dashboard: available balance, spend today and this month, estimated runway, spend and request charts, reliability at a glance, budget usage, and top agents & keys. |
| **Activity** | Deep usage analytics with four views (Overview, Trends, Performance, Explore), filters by key/agent and model, and CSV export. |
| **Budgets & Limits** | Two sub-tabs: **Spend Limits** (account-wide daily/monthly/lifetime caps and per-service limits) and **Balance & Top-up** (manual top-ups, auto top-up, the transaction history and monthly usage). |
| **Logs** | The per-request audit trail: every request's model, tokens, cost, speed, and status, with live refresh, search, filters, and a detail drawer. |
| **Settings** (cog) | **Models** (enable/disable catalog models, edit the Auto Router), **Service Access & API Keys** (connected services, API keys, and the API connection details), and **Guardrails** (content protections and custom content filters). |
## The Overview dashboard
The Overview tab summarises your account at a glance.

*Figure 1: The stat band (Available balance, Spent today, Spent this month and Estimated runway) and the quick-glance chips for auto top-up, limits, models and connected services*
Under the stat band, chips summarise auto top-up, spend limits, enabled models and how many connected services are online. A **Low balance** chip appears beside them when the balance is at or below the auto top-up threshold.
- **Available balance** — the prepaid credit remaining right now.
- **Spent today** / **Spent this month** — usage charges in the current day and calendar month (facility local time).
- **Estimated runway** — *"Rough estimate based on average daily spend this month. Top up or enable auto top-up to extend it."*

*Figure 2: Spend over time (last 14 days, with the change against the previous period) and Spend by model*

*Figure 3: Requests over time by model, and Reliability & speed with error rate, p95 latency and latency over time*

*Figure 4: Budget usage and Top agents & keys ranked by spend*

*Figure 5: The Activity heatmap: a year of usage, switchable between Tokens, Spend and Requests, with the longest streak and daily and weekly averages*
## Documentation guide
### Concepts
- **[How it works](https://docs.performancehub.co/module-docs/model-router/how-it-works)** — the conceptual model: prepaid balance vs spend limits, the two access paths, how requests are priced, and the full glossary.
### Getting started
- **[Getting started](https://docs.performancehub.co/module-docs/model-router/getting-started)** — enabling the module, your first top-up, and your first request.
- **[Connecting your application](https://docs.performancehub.co/module-docs/model-router/connecting-your-application)** — the quickstart for your own software: base URL, authentication, and curl / Python / TypeScript examples.
### Managing access
- **[Models](https://docs.performancehub.co/module-docs/model-router/models)** — the catalog, enabling and disabling models, quick presets, and the Auto Router.
- **[API keys](https://docs.performancehub.co/module-docs/model-router/api-keys)** — creating keys, the one-time secret display, per-key spend caps, rate limits, model restrictions, and the rotate/revoke/delete lifecycle.
- **[Connected Services](https://docs.performancehub.co/module-docs/model-router/ai-agents)** — how paired AI Agents and Workflows connect, and how to bound each one.
- **[Guardrails](https://docs.performancehub.co/module-docs/model-router/guardrails)** — content protections (PII masking, secret redaction, prompt-injection shield) and custom content filters.
### Money & monitoring
- **[Budgets & top-ups](https://docs.performancehub.co/module-docs/model-router/budgets-and-topups)** — the balance, manual and auto top-ups, receipts, the transaction history, monthly usage, and spend limits.
- **[Activity & logs](https://docs.performancehub.co/module-docs/model-router/activity-and-logs)** — the analytics views, CSV export, and the request log.
### Reference
- **[Reference](https://docs.performancehub.co/module-docs/model-router/reference)** — every setting, default, limit, and error response in one place.
- **[Troubleshooting](https://docs.performancehub.co/module-docs/model-router/troubleshooting)** — symptom-by-symptom fixes for blocked requests, key issues, and balance questions.
- **[Common questions](https://docs.performancehub.co/module-docs/model-router/common-questions)** — FAQ.
## Getting started checklist
Tick items off as you go — your progress is saved in this browser, and you can print the list.
),
},
{
id: 'top-up',
label: (
<>
Top up your balance — Budgets & Limits →{' '}
Balance & Top-up. Minimum top-up is $10.
See{' '}
Budgets & top-ups
.
>
),
},
{
id: 'auto-top-up',
label: (
<>
Turn on auto top-up (recommended) — so
inference never stops because the balance ran out.
>
),
},
{
id: 'spend-limits',
label: (
<>
Set spend limits (recommended) — Budgets &
Limits → Spend Limits. A daily or monthly cap
bounds the worst case. See{' '}
Budgets & top-ups
.
>
),
},
{
id: 'connect',
label: (
<>
Connect something — a paired AI Agent connects
automatically; for your own software, create an API key under
Settings → Service Access & API Keys. See{' '}
Connecting your application
.
>
),
},
{
id: 'verify',
label: (
<>
Verify in the Logs tab — after a test request,
confirm it appears with the expected model, tokens, and cost.
>
),
},
],
},
]}
/>
## Selecting a facility
The module is configured per facility. Use the facility selector in the main Performance Hub toolbar to choose which facility you are configuring. While the facility context loads, the module shows *"Loading facility context…"*.
Source: https://docs.performancehub.co/module-docs/model-router/overview
---
# How it works
This page explains the concepts behind **Model Router** — read it once and every other page will make more sense.
## The prepaid balance
Your facility holds a prepaid balance in USD. It only moves in two directions:
- **Up** when you top up (manually, via auto top-up, or by an adjustment credited to your account). Top-ups appear instantly.
- **Down** as usage accrues. Usage is reported live by the inference service and reconciled against your prepaid balance every 15 minutes, so very recent activity can take a few minutes to appear in **Monthly usage**. The transaction history lists top-ups and balance adjustments only.
The balance is the financial wall: when it reaches zero, inference requests are blocked with an *insufficient credits* error until you top up. Listing models still works with a zero balance, so integrations can discover the catalog before you fund the account.
## The model catalog
The catalog contains models from many providers, each priced per million tokens (input and output priced separately, shown in the Models settings). All models are enabled by default for your facility; you can:
- **Disable models account-wide** in Settings → Models (at least one model must stay enabled).
- **Restrict models per API key or per connected service** — a tighter list on top of the facility's enabled set.
A special catalog entry, **`auto`** (the **Auto Router**), has no price of its own. For each request it automatically selects the best model based on the prompt's complexity, and you're billed at the rate of the model it picks. See [Models](https://docs.performancehub.co/module-docs/model-router/models).
## Two ways to consume models
### 1. Connected Services
AI Agent devices paired with your facility connect to Model Router automatically — no API key is ever entered on the device — and Workflows can connect as well. Each connected service appears in Settings → Service Access & API Keys under **Connected Services** with its own spend figures, and you can give each one its own spend limits, model restrictions, and guardrail overrides. See [Connected Services](https://docs.performancehub.co/module-docs/model-router/ai-agents).
### 2. Your own API keys
You can provision API keys (they start with `phk_`) so third-party software and systems that support BYOK (Bring Your Own Key) can connect to models published by Performance Hub. The endpoint is OpenAI-compatible — point any OpenAI SDK or tool at the base URL and authenticate with your key. See [API keys](https://docs.performancehub.co/module-docs/model-router/api-keys) and [Connecting your application](https://docs.performancehub.co/module-docs/model-router/connecting-your-application).
Both paths draw from the same balance, respect the same account-wide spend limits, appear in the same Activity analytics and Logs, and can be individually bounded.
## Balance vs spend limits
These are two different mechanisms, and both apply:
| | Prepaid balance | Spend limits |
|---|---|---|
| **What it is** | The money you have credited | Policy caps you choose |
| **What happens at the boundary** | Requests blocked when balance hits $0 | Requests blocked when the cap is reached |
| **How it recovers** | Top up (or auto top-up) | Daily and monthly limits reset automatically; the lifetime hard cap resets only when you reset it |
| **Scope** | Whole facility | Whole facility (Account Limits), plus optional tighter per-key / per-service limits |
Account spend limits come in three windows — **daily** (resets every day at midnight, facility local time), **monthly** (resets on the 1st of each month, facility local time), and a **lifetime hard cap** (counts spend since the last manual reset; never resets on its own). Any combination can be active at once, and limits apply across every API key and connected service combined. The prepaid balance still applies on top.
Per-key and per-service limits work the same way (daily, monthly, lifetime) but count only that key's or service's spend. When a limit is hit, only that key or service is blocked — the rest of the facility keeps working.
## How a request is processed
When a request arrives at the endpoint, checks run in this order:
1. **Authentication** — a valid API key or connected-service identity. For an API key, a facility where Model Router isn't enabled is rejected here with HTTP 403 (*"Model Router is not enabled for this facility"*). Listing models (`GET /v1/models`) is answered straight after authentication, so it keeps working at a zero balance.
2. **Prepaid credit check** — available balance must be above $0 (for connected services, Model Router must also be enabled); otherwise the request is rejected with an *insufficient credits* error (HTTP 402).
3. **Account spend limits** — if a daily, monthly, or lifetime cap is breached, the request is rejected with a *budget exceeded* error (HTTP 402) that states when the window resets, in your facility's timezone.
4. **Per-key / per-service spend limits** — same check, scoped to the calling key or service.
5. **Prompt checks** — the prompt-injection shield and your custom content filters run on the prompt. A *block* filter or an injection hit rejects the request (HTTP 403, *content policy violation*); *redact* filters rewrite matches to `[REDACTED]` and let the request continue.
6. **Model routing** — `auto` is resolved to a concrete model by the Auto Router (a request containing an image is sent to a vision-capable model you allow). Fallback models are filtered to the ones the key may use, your reasoning setting is applied when the request does not set its own, and requested output limits are clamped to what the model supports.
7. **Inference** — PII masking and secret redaction (when enabled) are applied by the inference service before the prompt reaches the model; the request then goes to the model provider and the response streams back.
Rate limits (requests/min and tokens/min, if set on the key or service) are enforced throughout; exceeding them returns HTTP 429.
## What is recorded
Every request is logged with its metadata: time, model (both the requested model and the one the router resolved), provider, tokens (prompt, completion, cached, reasoning), cost, latency, time to first token, speed, status, and the calling key or service. **Prompt and response content is never stored.**
Usage and spend figures shown in the module are the same figures used for enforcement, so what you see as "limit reached" is exactly what gets blocked.
## Glossary
| Term | Meaning |
|------|---------|
| **Available balance** | Prepaid credit remaining right now (credited total minus live spend). |
| **Auto Router** | The `auto` model — routes each request to a concrete model by prompt complexity; billed at the resolved model's rate. |
| **Auto top-up** | Automatically charges your stored card when the balance falls below a threshold you set. |
| **BYOK** | "Bring Your Own Key" — third-party software that accepts a custom OpenAI-compatible endpoint and API key. |
| **Connected service** | A paired AI Agent or a Workflow connected to Model Router. Each uses its own identity, spend limits, model restrictions and guardrail overrides, without an API key you manage. |
| **Content filter** | A custom regular expression evaluated on prompt content; matches are redacted or the request is blocked. |
| **Estimated runway** | A rough estimate of how long the balance lasts at this month's average daily spend. |
| **Guardrail** | A built-in content protection (PII masking, secret redaction, prompt-injection shield) applied facility-wide or per key or service. |
| **Lifetime hard cap** | A spend ceiling counting spend since its last manual reset; it never resets on its own. |
| **Monthly usage** | Usage drawn from the balance, grouped by calendar month (UTC), with a usage statement (PDF) and usage detail (CSV) for each month. |
| **Rate limits** | Per-key/per-service caps on requests per minute and tokens per minute. |
| **Reconciled spend** | Usage reconciled against your prepaid balance every 15 minutes. |
| **Spend limit** | A policy cap (daily / monthly / lifetime) that blocks requests when reached, independent of the balance. |
| **Top-up** | A charge to your stored billing card that credits the prepaid balance. |
| **Transaction history** | The list of top-ups and balance adjustments, with the balance after each. Usage is summarised in Monthly usage instead. |
Source: https://docs.performancehub.co/module-docs/model-router/how-it-works
---
# Getting started
This guide takes a facility from nothing to a working first request in three steps, then shows how to check it worked.
## Before you begin
- You need to be an **Organisation Administrator**. Model Router is available to Organisation Administrators only, at Technology Stack facilities.
- Select the facility you want to configure using the facility selector in the main Performance Hub toolbar.
- Have a billing card stored for the facility, or be ready to add one. Top-ups charge your stored billing card; if none is on file, the Balance & Top-up page shows *"No card on file."* with an **Add payment details** link.
## Step 1 — Enable Model Router
Open **Model Router** from the main menu (in the **AI Modules & Agents** group). If the module has not been enabled for this facility yet, you see the **Welcome to Model Router** page:
> *"Give your business prepaid access to a catalog of leading AI models through a single, OpenAI-compatible endpoint. Your Performance Hub AI Agents connect automatically, and you can create API keys to plug the same models into your own software — with spend limits and full usage visibility."*

*Figure 1: The Welcome to Model Router page a facility sees before the module is enabled*
The page has three buttons: **Enable Model Router**, **Read the documentation** (which opens these docs) and **Explore first without enabling**. Below them, three steps lay out the setup:
| Step | What it says | Button |
|------|--------------|--------|
| **1. Enable & add a billing card** | *"Turn on prepaid AI inference for this facility and store a billing card if you have not already — nothing is charged until you top up."* | **Enable module** |
| **2. Top up your balance** | *"Add prepaid credit with your stored billing card (minimum $10). Turn on auto top-up so inference never stops because the balance ran out."* | **Top up** |
| **3. Connect an agent or create an API key** | *"Paired AI Agent devices connect automatically. For your own software, create an API key and point any OpenAI-compatible SDK at our endpoint."* | **Create an API key** |
Each step also has a **Learn more** link.
Click **Enable Model Router** (or **Enable module**) to turn the module on.
### The setup checklist
After you enable the module, a setup checklist banner shows at the top of Model Router until setup is complete. It is titled **Finish setting up Model Router** and shows a time estimate (e.g. *~3 min left*), how many steps are complete (e.g. *2 of 3 complete*) and a **Resume** button that expands the full checklist. Each step has **Review** (or **Review again**) and **Learn more**; **Collapse** folds it away again. When one step is left, the expanded subtitle reads *"Just one step to go before AI requests start flowing. Steps can be done in any order."*

*Figure 2: The setup checklist banner shown until setup is complete*
The required steps are **Add a billing card**, **Top up your balance** and **Connect an agent or create an API key**. Optional steps — **Set spend limits**, **Turn on auto top-up** and **Verify your first request** — can be dismissed with **Skip for now**.
## Step 2 — Make your first top-up
Go to **Budgets & Limits** → **Balance & Top-up**.

*Figure 3: The Top up balance card (presets, amount, stored card) and the Auto top-up card*
1. In the **Top up balance** card, pick a preset (**$25**, **$50**, **$100**, **$250**, **$500**, **$1000**) or type an amount. *"Minimum top-up is $10."*
2. Check the stored card shown beneath the amount (e.g. *"Amex ending 0005 · exp 06/29"*). Use **Edit payment details** if it needs updating.
3. Click **Top up $–.––**. On success you'll see *"Top-up successful. Your balance has been credited."* Top-ups appear in the balance instantly.
**Recommended:** in the **Auto top-up** card, switch on **Enable auto top-up**, set **When balance is below** and **Top up by** (also minimum $10), and click **Save changes**. Your card is then charged automatically whenever the balance falls below the threshold, so inference never stops unexpectedly. See [Budgets & top-ups](https://docs.performancehub.co/module-docs/model-router/budgets-and-topups) for details.
## Step 3 — Send your first request
Pick whichever path applies to you:
### If you have AI Agents or Workflows
Nothing to configure — AI Agent devices paired with this facility connect automatically, and Workflows can connect too. Open **Settings** (cog) → **Service Access & API Keys** and confirm your agent appears under **Connected Services** with status **Online**. See [Connected Services](https://docs.performancehub.co/module-docs/model-router/ai-agents).
### If you're connecting your own software
1. Open **Settings** (cog) → **Service Access & API Keys** and click **Create key**. Copy the secret from the *"Copy your API key"* dialog — *"This is the only time the full key is shown. Store it somewhere safe."*
2. Use the **Base URL** and example request shown in the **Connecting to the API** card on the same page. A minimal test:
```bash
curl https://app.performancehub.co/ai/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "auto", "messages": [{"role": "user", "content": "Hello"}]}'
```
See [Connecting your application](https://docs.performancehub.co/module-docs/model-router/connecting-your-application) for Python and TypeScript examples.
## Step 4 — Verify
Open the **Logs** tab. Your test request should appear within moments, with its model, token counts, cost, speed, and a **success** status. If it doesn't, see [Troubleshooting](https://docs.performancehub.co/module-docs/model-router/troubleshooting).
## Recommended next steps
- **Set spend limits** — Budgets & Limits → **Spend Limits**. A monthly cap is a sensible safety net; see [Budgets & top-ups](https://docs.performancehub.co/module-docs/model-router/budgets-and-topups).
- **Review the model catalog** — Settings → **Models**. All models are enabled by default; use the quick presets to narrow the set if you have provider or budget preferences. See [Models](https://docs.performancehub.co/module-docs/model-router/models).
- **Consider guardrails** — Settings → **Guardrails** for PII masking, secret redaction, and prompt-injection shielding. None are enabled by default. See [Guardrails](https://docs.performancehub.co/module-docs/model-router/guardrails).
Source: https://docs.performancehub.co/module-docs/model-router/getting-started
---
# Connecting your application
The Model Router endpoint is **OpenAI-compatible** — point any OpenAI SDK, framework, or BYOK-capable tool at the Performance Hub base URL, authenticate with one of your API keys, and it just works. This page is the integration quickstart.
## What you need
1. **An API key.** You need to be an Organisation Administrator to create one. Create it under Settings (cog) → **Service Access & API Keys** → **Create key**. Keys start with `phk_` and the secret is shown only once. See [API keys](https://docs.performancehub.co/module-docs/model-router/api-keys).
2. **The base URL.** Shown in the **Connecting to the API** card on the same settings page:

*Figure 1: Connecting to the API: the base URL and a copy-paste example request*
```text
https://app.performancehub.co/ai/v1
```
> The exact base URL for your environment is always shown in the module — copy it from there rather than from this page.
## Authentication
Send your key as a bearer token on every request:
```text
Authorization: Bearer phk_your_key_here
```
Treat the key like a password. If it leaks, **Rotate** it (a new secret is issued and the old one stops working immediately) or **Revoke** it from the API keys list.
## Your first request
### curl
```bash
curl https://app.performancehub.co/ai/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "auto", "messages": [{"role": "user", "content": "Hello"}]}'
```
*"Use model 'auto' to let the router pick the best model per request, or any model ID enabled for your facility."*
### Python (OpenAI SDK)
```python
from openai import OpenAI
client = OpenAI(
base_url="https://app.performancehub.co/ai/v1",
api_key="YOUR_API_KEY",
)
response = client.chat.completions.create(
model="auto",
messages=[{"role": "user", "content": "Hello"}],
)
print(response.choices[0].message.content)
```
### TypeScript / Node (OpenAI SDK)
```typescript
import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://app.performancehub.co/ai/v1',
apiKey: process.env.PH_API_KEY,
});
const response = await client.chat.completions.create({
model: 'auto',
messages: [{ role: 'user', content: 'Hello' }],
});
console.log(response.choices[0].message.content);
```
## Listing models
`GET /v1/models` returns every model enabled for your facility (honouring any per-key model restriction). Model listing works even when your balance is zero, so integrations can discover the catalog before the account is funded.
```bash
curl https://app.performancehub.co/ai/v1/models \
-H "Authorization: Bearer YOUR_API_KEY"
```
Use any returned model ID in the `model` field, or use `auto` to let the [Auto Router](https://docs.performancehub.co/module-docs/model-router/models) pick the best model per request.
## Streaming
Streaming works the standard OpenAI way — set `"stream": true` and consume the server-sent event chunks. The SDK examples above support it via their usual streaming APIs.
## Model fallbacks
You can supply an ordered fallback chain so that if the primary model fails, the request is retried on the next one:
- `models: ["model-a", "model-b", "model-c"]` — a full ordered list; the first entry is the primary.
- or `model: "model-a"` plus `fallbacks: ["model-b", "model-c"]` — backups tried after the primary.
Fallback candidates are filtered to the models your key is allowed to use.
## Error responses
Errors use the standard OpenAI error envelope: `{ "error": { "message", "type", "code" } }`.
| HTTP | `type` | `code` | Meaning | What to do |
|------|--------|--------|---------|------------|
| 401 | `invalid_request_error` | `invalid_api_key` | Missing API key, or invalid connected-service credentials. | Check the `Authorization: Bearer` header. |
| 401 | `invalid_request_error` | — | Unknown or revoked API key (*"Invalid API key"*). | Re-activate the key or create a new one. |
| 402 | `insufficient_quota` | `insufficient_credits` | Prepaid balance depleted (or, for connected services, the facility is not enabled). | Top up (or enable auto top-up). |
| 402 | `insufficient_quota` | `budget_exceeded` | A spend limit was reached — facility-wide, or on this key or connected service. The message states which window and, for daily/monthly limits, exactly when it resets in the facility's timezone. The body also carries `window`, `resetsAt`, `timezone` and `docsUrl` fields. | Wait for the reset, raise the limit, or reset the hard cap. |
| 403 | `invalid_request_error` | — | *"Model Router is not enabled for this facility"* (API keys), or *"Facility has no provisioned inference key"* (the key has no backing inference key). | Enable Model Router for the facility (see [Getting started](https://docs.performancehub.co/module-docs/model-router/getting-started)). If it's already enabled, rotate the key or create a new one. |
| 403 | `invalid_request_error` | `content_policy_violation` | *"Request blocked by content policy"* — a block-mode content filter or the prompt-injection shield matched the prompt. | Review [Guardrails](https://docs.performancehub.co/module-docs/model-router/guardrails) settings. |
| 429 | — | — | Rate limit exceeded (requests/min or tokens/min set on your key). | Back off and retry; raise the key's rate limits if needed. |
| 502 | `server_error` | — | The upstream inference service is temporarily unavailable. | Retry with backoff. |
Both 402 errors include a `docsUrl` field, and their message ends with a link to the Performance Hub docs.
Handle 402 gracefully in your integration — it's the expected signal when an account runs out of credit or hits a budget, not a fault.
## Good practices
- **One key per system.** Give each integration its own labelled key so usage is attributable in Activity and Logs, and a leak only requires rotating one key.
- **Bound each key.** Set per-key spend caps and rate limits appropriate to the system; see [API keys](https://docs.performancehub.co/module-docs/model-router/api-keys).
- **Prefer `auto`** unless you need a specific model — you get fast, cheap models for simple work and the strongest models only when a task needs them.
- **Watch the Logs tab** during integration — every request appears with its status, tokens, and cost, which makes debugging integration issues fast. See [Activity & logs](https://docs.performancehub.co/module-docs/model-router/activity-and-logs).
Source: https://docs.performancehub.co/module-docs/model-router/connecting-your-application
---
# Models
The **Models** page (Settings cog → **Models**) is your facility's model catalog. *"Turn models on or off for this account. All models are enabled by default. Individual keys and agents can be restricted further."*
At the top of the page, the **Automatically enable new models** switch decides what happens when the catalog grows: *"When a new model is added to the catalogue, turn it on for this account automatically. Turn this off to review new models yourself before they can be used."*
For the live price of every model, see the [Model Catalog](https://docs.performancehub.co/module-docs/platform-pricing/model-router/model-catalog), and for how models compare on capability and value, see the [Model Rankings](https://docs.performancehub.co/module-docs/platform-pricing/model-router/model-rankings).

*Figure 1: Available models: Automatically enable new models, quick presets, filters and the Auto Router card at the top of the list*
## The model list
Models are grouped by provider, each group showing a region badge (e.g. **US**) and an enabled count (e.g. *7/7*); the list header shows the overall count (e.g. *117 / 117 enabled*). Each model row shows:
- The model ID (e.g. `anthropic/claude-sonnet-4-5`)
- A **Copy model ID** button next to the ID
- Context window size (e.g. *200K context*, *1M context*)
- Pricing per million tokens, input and output separately (e.g. *$3.15 in · $15.75 out / 1M tokens*)
- Capability icons for what the model accepts: text, image, audio and PDF (hover an icon for its meaning, e.g. *"Supports image input"*)
- A reasoning setting (e.g. **Reasoning: Default**), or a **No reasoning** badge for models that take no reasoning setting
- An enable/disable toggle

*Figure 2: A provider group with each model's context size, pricing, reasoning setting and toggle*
Toggle models on or off, then click **Save changes**. *"At least one model must stay enabled."* Each provider group has a **Disable all** / **Enable all** shortcut.
Disabling a model here blocks it for the whole facility — every API key and connected service. To narrow the list for just one key or service instead, use its **Restrict models** option (see [API keys](https://docs.performancehub.co/module-docs/model-router/api-keys) and [Connected Services](https://docs.performancehub.co/module-docs/model-router/ai-agents)).
## Reasoning setting
Each reasoning-capable model has a reasoning setting: the reasoning effort applied when a client does not request one. It is useful for tools that cannot set reasoning effort themselves; a client that sends its own level still wins.

*Figure 3: A model's reasoning setting open, showing Reasoning: Default and the effort levels*
- **Reasoning: Default** leaves the provider's default in place.
- **None**, **Minimal**, **Low**, **Medium**, **High** and **Max** set the effort level used when a request does not include one. Some providers accept only certain levels.
- The setting can't be changed while the model is turned off.
- Models that do not accept a reasoning setting show **No reasoning** instead: *"This model does not accept a reasoning setting, so no default can be configured for it."*
Click **Save changes** to apply.
## Quick presets
Presets apply a curated selection in one click (then **Save changes** to apply):
| Preset | Selection |
|--------|-----------|
| **All models** | *"Enable every available model."* |
| **Frontier** | *"Flagship models from Western labs (OpenAI, Anthropic, Google, xAI) plus the Auto Router."* |
| **Western providers** | *"Exclude models from Chinese providers (data-sovereignty friendly)."* |
| **Open weights** | *"Only models with openly available weights (Llama, Mistral, Qwen, DeepSeek…)."* |
| **Budget** | *"Low-cost models — input price at or below $1.00 per million tokens."* |
## Search, filters, and sorting
- **Search** matches model and provider names (*"Search models or providers"*).
- **Status** — Any / Enabled / Disabled.
- **Region** — United States / Europe / China.
- **Capability** — Vision (image input), Audio (input or output), PDF input, Reasoning.
- **Sort By** — Recommended / Name / Price / Context size, ascending or descending.
If nothing matches you'll see *"No models match your filters."* with a **Clear filters** action.
## The Auto Router (`auto`)
The **Auto Router** sits at the top of the list as the model ID `auto`, marked *"Usage-priced"*:
> *"The Auto Router has no price of its own. For each request it automatically selects the best model based on the prompt's complexity, and you're billed at the rate of the model it picks. It's the recommended default — agents get fast, cheap models for simple work and the strongest models only when a task needs them."*
Its info hint sums it up: *"The Auto Router picks the best model for each request based on complexity. Callers use the single model "auto"; you decide which model handles each tier."*
The card shows the current routing — which model handles each complexity tier:
| Tier | Handles |
|------|---------|
| **Simple** | *Short, straightforward prompts* |
| **Medium** | *Everyday tasks* |
| **Complex** | *Hard, multi-step work* |
| **Reasoning** | *Deep reasoning & analysis* |
The current tier targets, with their prices, are published on the [model catalog](https://docs.performancehub.co/module-docs/platform-pricing/model-router/model-catalog) page.
In Activity and Logs, requests sent to `auto` show the **model that actually served them**; the request detail drawer records `auto` as the **Requested model**. This is expected — it's how you see what the router chose.
### Editing the routing
Click **Edit routing** on the Auto Router card to customise the tier map for your facility:

*Figure 4: The Edit Auto Router dialog: one model for each complexity tier*
> *"Choose which model handles each complexity tier. Callers still use the single model "auto"; requests are routed to the model you pick here based on the prompt's complexity. Only enabled models can be selected."*
Pick a model for every tier (*"Pick a model for every tier to save."*), then **Save routing**. **Reset to default** returns your facility to the standard routing.
If your facility restricts the enabled-model list, keep the router's target models enabled — a routed request needs its resolved model to be available.
## Missing a model?
The side card on the Models page: *"Want to see a model added? Raise a support request and we'll add it if it's generally available."* Use the **Raise a support request** link.
Source: https://docs.performancehub.co/module-docs/model-router/models
---
# API keys
API keys let your own software — or any third-party system that supports BYOK (Bring Your Own Key) — use the models published by Performance Hub. Keys are managed under Settings (cog) → **Service Access & API Keys**. You need to be an Organisation Administrator to manage them.
> *"Provision API keys so third-party software and systems that support BYOK (Bring Your Own Key) can connect to models published by Performance Hub."*

*Figure 1: The API keys list: label, masked key, status, spend and limits, models, last used and the row actions*
## The keys list
| Column | Meaning |
|--------|---------|
| **Label** | The optional name you gave the key. |
| **Key** | The masked key (e.g. `phk_7Qm2…Xa9LkP`) — the full secret is never shown again after creation. |
| **Status** | **Active**, **Provisioning…** (briefly, while a new key is set up), or **Disabled** (revoked; the row is dimmed). |
| **Spend / limits** | Lifetime spend, and progress toward any spend limits set on the key. |
| **Models** | *all*, or the number of models the key is restricted to. |
| **Last used** | When the key last made a request. |
| **Actions** | **Edit**, **Rotate**, **Revoke** (or **Re-activate**), **Delete**. |
If you use Workflows, a **Workflows** row shows **Managed service** in place of a masked key. With no keys at all, the list shows *"No API keys yet."*
## Creating a key
Click **Create key**. The **Create API key** drawer opens:

*Figure 2: The Create API key drawer: label, connection type, spend limits, rate limits, model restriction and guardrail override*
| Setting | Behaviour |
|---------|-----------|
| **Label (optional)** | A name for the key, usually the system it's for (for example *Hotel Concierge App*, *Clinic Intake Assistant* or *Booking Website*). Strongly recommended — labels are how usage is attributed in Activity and Logs. |
| **Connection type** | **API key** — *"Generic API keys remain in the API Keys list."* **External agent** — *"External agents appear under Connected Services and use this key to authenticate."* Set when the key is created; it can't be changed later. |
| **Set spend limits for this key** | *"Set any combination of daily, monthly and lifetime caps for this key."* — **Daily limit** (*"Resets at the start of each day (facility timezone)."*), **Monthly limit** (*"Resets on the 1st of each month (facility timezone)."*), and **Lifetime cap** (*"Total spend over the lifetime of this key. Never resets."*). |
| **Rate limits (optional)** | *"Cap requests and tokens per minute for this key. Leave blank for unlimited."* — **Requests / min** and **Tokens / min**. |
| **Restrict models for this key** | *"Only the selected models will be usable with this key."* Pick from the facility's enabled models. |
| **Override facility guardrails for this key** | *"Select none to explicitly disable guardrails for this key."* When on, this key uses its own guardrail selection instead of the facility's — including "none". See [Guardrails](https://docs.performancehub.co/module-docs/model-router/guardrails). |
Click **Create key**.
### The one-time secret
The new key is shown once, in the **Copy your API key** dialog:

*Figure 3: The new key is shown once: copy it before you close the dialog*
> *"This is the only time the full key is shown. Store it somewhere safe."*
Copy it into your secret manager before clicking **Done**. After this dialog closes, only the masked form is ever displayed. If you lose the secret, **Rotate** the key to get a new one.
## Editing a key
Click a key's row (or its **Edit** pencil) to open the **Configure API key** drawer — the same settings as creation (label, spend limits, rate limits, model restriction, guardrail override):

*Figure 4: The Configure API key drawer*
Changes take effect within moments of saving. When a key has daily/monthly limits, its row shows usage against them (**Today**, **This month**, **Lifetime**) and a **Limit reached** badge when one is hit.
## Key lifecycle: rotate, revoke, delete
Each action has a confirmation dialog quoting exactly what will happen:
| Action | Effect | Confirmation copy |
|--------|--------|-------------------|
| **Rotate** | Issues a new secret (shown once) with the same settings; the old secret stops working immediately. | *"A new key will be issued and the current key will stop working immediately."* *"You will need to reconfigure any services still using the old key. This cannot be undone."* |
| **Revoke** | Disables the key immediately, reversibly. | *"This key will be disabled immediately and any services using it will stop working. You can re-activate it again later."* |
| **Re-activate** | Restores a revoked key (same secret). | None — runs immediately. |
| **Delete** | Removes the key permanently. | *"This permanently deletes the key and any services using it will stop working. This cannot be undone."* |

*Figure 5: The Delete API key confirmation*
**Which to use:** rotate on suspicion of a leak (service keeps its settings and history — just update the secret); revoke to pause a system temporarily; delete only when the integration is gone for good.
## Connecting with a key
The **Connecting to the API** card below the keys list shows the base URL and a copy-paste example request for your environment. See [Connecting your application](https://docs.performancehub.co/module-docs/model-router/connecting-your-application) for the full quickstart with SDK examples and error handling.
Source: https://docs.performancehub.co/module-docs/model-router/api-keys
---
# Connected Services
Connected services are the Performance Hub services that use Model Router with their own identity rather than an API key you manage. They include **AI Agent devices** paired with your facility, which connect **automatically** — no API key is ever created, copied, or entered on the device — and **Workflows**, which can connect too. Each service's access can be individually configured and bounded.
> *"Services connected to Model Router use their own identity, spend limits, model restrictions and guardrail overrides."*
## The Connected Services card
Open Settings (cog) → **Service Access & API Keys**. The **Connected Services** card lists every connected service:

*Figure 1: Connected Services: each AI Agent or workflow with its type, status, spend and limits, and last use*
| Column | Meaning |
|--------|---------|
| **Service** | The service's name; for an AI Agent, also its location at your facility. |
| **Type** | **Agent** or **Workflow**. |
| **Status** | For an AI Agent, **Online** or **Offline** — live device connectivity. A connected workflow shows **Active**. |
| **Spend / limits** | The service's spend, and progress toward any per-service limits (**Today**, **This month**, **Lifetime**, with a **Limit reached** badge when hit). |
| **Last used** | The service's most recent request. |
| **Actions** | **Configure** — opens the service's configuration drawer. |
If nothing is connected yet: *"No services are connected yet. Pair an AI Agent or open Workflows to connect one."* Pairing devices is done in the [AI Agent](https://docs.performancehub.co/module-docs/ai-agent/overview) module, not here.
## Configuring a service
Click the **Configure** action to open the **Configure agent** drawer (**Configure workflow** for a workflow). The options mirror API-key configuration, scoped to the service:
| Setting | Behaviour |
|---------|-----------|
| **Set spend limits for this agent** | *"Set any combination of daily, monthly and lifetime caps for this agent."* (for a workflow, the labels read "…this workflow"). Daily resets at the start of each day and monthly on the 1st of each month, in the facility timezone; the lifetime cap never resets. |
| **Rate limits (optional)** | *"Cap requests and tokens per minute for this agent. Leave blank for unlimited."* |
| **Restrict models for this agent** | *"Only the selected models will be usable by this agent."* |
| **Override facility guardrails for this agent** | *"Select none to explicitly disable guardrails for this agent."* |
When a service hits one of its spend limits, only that service is blocked (with a clear *budget exceeded* message stating when the window resets); other services and API keys keep working. Account-wide limits still apply on top — see [Budgets & top-ups](https://docs.performancehub.co/module-docs/model-router/budgets-and-topups).
You can also set per-service spend limits from **Budgets & Limits** → **Spend Limits** → **Per-service spend limits** — it edits the same configuration.
## Services and credits
Connected services draw from the same prepaid balance as API keys. If the module is not enabled for the facility, or the balance is depleted, they cannot run inference — an AI Agent's chat shows a credits-required state until the facility is enabled and topped up. Keeping **auto top-up** on is the easiest way to make sure agents never stop mid-conversation; see [Budgets & top-ups](https://docs.performancehub.co/module-docs/model-router/budgets-and-topups).
## Services in analytics
Connected services appear alongside API keys everywhere usage is shown: the Overview tab's **Top agents & keys**, the Activity tab's **Top API keys & agents** and **Key / agent** filter, and the Logs tab's **Agent / Key** column. This makes it easy to see exactly what each service is spending and on which models, whether it's a front desk assistant in a hotel lobby or an intake assistant in a clinic waiting room. See [Activity & logs](https://docs.performancehub.co/module-docs/model-router/activity-and-logs).
## Security model (in brief)
Each AI Agent authenticates with short-lived, per-device credentials that Performance Hub manages and rotates automatically. There is no static API key on the device to leak, and decommissioning a device revokes its access.
Source: https://docs.performancehub.co/module-docs/model-router/ai-agents
---
# Guardrails
Guardrails apply content protections to AI requests before they reach the model. They are configured facility-wide under Settings (cog) → **Guardrails**, and individual API keys and connected services can override them.
> *"Apply content protections to every request for this facility. None are enabled by default. Individual keys can override these."*

*Figure 1: Guardrails: the templates and PII masking*

*Figure 2: Secret redaction and the prompt-injection shield*
## The built-in guardrails
| Guardrail | What it does |
|-----------|--------------|
| **PII masking** | *"Detect and mask personal data (names, emails, phone numbers, etc.) before prompts reach the model."* |
| **Secret redaction** | *"Strip API keys, tokens and other secrets out of prompts before they are sent upstream."* |
| **Prompt-injection shield** | *"Block common prompt-injection / jailbreak attempts (e.g. "ignore previous instructions", "developer mode") before they reach the model."* |
PII masking and secret redaction **rewrite** the prompt (the request still goes through, with sensitive spans masked). The prompt-injection shield **blocks** matching requests outright with a *content policy violation* error (HTTP 403).
## Templates
The **Templates** row applies a preset selection in one click, then **Save changes**:
| Template | Selection |
|----------|-----------|
| **None** | No guardrails (the default). |
| **PII protection** | PII masking. |
| **Secret redaction** | Secret redaction. |
| **Injection shield** | Prompt-injection shield. |
| **Strict** | All three. |
Saving shows *"Guardrail settings saved."*
## Content filters
Below the guardrails, **Content filters** let you add your own rules: custom regular expressions evaluated on prompt content. **Redact** rewrites matches to `[REDACTED]`; **Block** rejects the request. Individual keys can override them.
Click **Add filter**, then set:
| Field | Behaviour |
|-------|-----------|
| **Pattern** | A regular expression (*"Regular expression, e.g. \b\d{16}\b"* — that example matches 16-digit card numbers). Unsafe or overly long patterns are dropped and not applied; patterns are capped at 300 characters, and a facility can have up to 25 filters. Matching ignores case, and only the first 20,000 characters of each message are checked. |
| **Action** | **Redact** — matches are rewritten to `[REDACTED]` and the request continues; **Block** — the request is rejected with a *content policy violation* error. |
Use **Remove** to delete a filter, then **Save changes**. With none configured: *"No content filters. Add one to redact or block matching prompt text."*
## Per-key and per-service overrides
Each API key and each connected service has an **Override facility guardrails** toggle in its configuration drawer:
- **Off (default):** the key or service inherits the facility's guardrail and content-filter selection.
- **On:** the key or service uses its own selection instead — *"Select none to explicitly disable guardrails for this key."* This is how you exempt one trusted integration from facility-wide masking, or apply stricter rules to a single key.
See [API keys](https://docs.performancehub.co/module-docs/model-router/api-keys) and [Connected Services](https://docs.performancehub.co/module-docs/model-router/ai-agents).
## What callers see
- **Redactions are silent** — the model simply receives `[REDACTED]` (or masked PII/secrets) in place of the matched text.
- **Blocks return HTTP 403** with the message *"Request blocked by content policy"* and code `content_policy_violation`. See the error table in [Connecting your application](https://docs.performancehub.co/module-docs/model-router/connecting-your-application).
Prompt content is scanned in transit only — guardrails never store what they scan.
Source: https://docs.performancehub.co/module-docs/model-router/guardrails
---
# Budgets & top-ups
The **Budgets & Limits** tab is where money is managed. It has two sub-tabs: **Spend Limits** (policy caps) and **Balance & Top-up** (the balance, top-ups, the transaction history and monthly usage).
For the rates that produce these charges, see the [Model Catalog](https://docs.performancehub.co/module-docs/platform-pricing/model-router/model-catalog).
## Balance & Top-up
The header band shows **Available balance**, **Spent today**, **Spent this month**, and **Estimated runway** (*"Rough estimate based on average daily spend this month. Top up or enable auto top-up to extend it."* — shown as e.g. *3 days*, *1 day*, *0 days*, *365+ days*, or *Not enough data*).
### Manual top-up

*Figure 1: The Top up balance and Auto top-up cards*
*"Charge your stored billing card to add credit."*
1. Pick a preset — **$25**, **$50**, **$100**, **$250**, **$500**, **$1000** — or enter an amount. *"Minimum top-up is $10."* (A single top-up can be at most $5,000.)
2. Confirm the stored card shown below the amount; **Edit payment details** / **Add payment details** takes you to your billing settings if needed.
3. Click **Top up $–.––**. Success shows *"Top-up successful. Your balance has been credited."* and the balance updates instantly.
If your card requires extra verification you'll see *"Additional card authentication is required. Please try again."*; a failed charge shows *"Top-up could not be completed."*
### Auto top-up
*"Automatically charge your stored card when the balance falls below the threshold."*
1. Switch on **Enable auto top-up**.
2. Set **When balance is below** (the threshold) and **Top up by** (the recharge amount, minimum $10).
3. Click **Save changes**.
Auto top-up runs on a schedule (checks happen roughly every 15 minutes). If your balance is already at or below the threshold when you save, the module tells you: *"Your balance (…) is already at or below this threshold. Auto top-up will charge your card at the next scheduled check (usually within 15 minutes) — top up now to credit your balance immediately and avoid any interruption."*
### Balance warnings
When auto top-up is off and the balance is projected to run out within 14 days, Overview, Spend Limits and Balance & Top-up warn (with a **Top up** link on Overview and Spend Limits): *"At your current spend, your balance runs out around … (~…). Top up or enable auto top-up."* If an auto top-up can't run, a red callout explains why (no card on file, extra card authentication needed, or the card was declined) with an **Update card** link.
| Callout | Cause |
|--------|-------|
| *"Auto top-up could not run because there is no card on file. Add a card to keep inference running."* | No stored card. |
| *"Auto top-up needs extra card authentication. Top up manually or update your card to continue."* | The card requires interactive verification. |
| *"Auto top-up failed (your card was declined). Update your card or top up manually to avoid interruption."* | Declined charge. |
### Transaction history

*Figure 2: Transaction history: top-ups and balance adjustments, with the balance after each one and receipts*
The transaction history lists top-ups and balance adjustments, with the columns **Date**, **Type**, **Amount**, **Balance after** and **Receipt**. Usage isn't listed here; it's summarised in [Monthly usage](#monthly-usage) below.
> *"Shows top-ups and balance adjustments. Usage is drawn down from your balance continuously and summarised in Monthly usage below."*
| Type | Meaning |
|------|---------|
| **Top-up** | A card charge crediting the balance (manual or auto top-up). |
| **Refund** | A refunded charge. |
| **Adjustment** | A balance correction credited or debited by Performance Hub. |
To download several receipts at once, tick the rows you want and click **Download receipts (n)**.
Click a row to open the **Transaction details** drawer:

*Figure 3: Transaction details, with the receipt download*
Top-ups carry a **Receipt number**, **Transaction ID**, **Payment reference**, the card used, and the source (e.g. *auto-recharge* for automatic top-ups), plus **Download receipt (PDF)**. A receipt for each top-up is also emailed to your facility's billing contacts.
### Monthly usage
Below the transaction history, **Monthly usage** shows the usage drawn from your balance, one row per calendar month.

*Figure 4: Monthly usage: usage drawn from the balance each month, with a usage statement (PDF) and usage detail (CSV) to download*
> *"Usage is reconciled against your prepaid balance every 15 minutes and grouped here by calendar month (UTC). Each month offers a summary statement (PDF) and a detailed export (CSV) — these are usage statements, not invoices; only card top-ups have receipts."*
- Use the year filter at the top right to choose which months are listed.
- The columns are **Month**, **Usage**, **Deductions** and **Downloads**. The current month carries a **Month to date** chip.
- Each month has **Download usage statement (PDF)** and **Download usage detail (CSV)**.
- Expand a month to see its **Usage charge** deductions. Long months show only the latest ones: *"Showing the latest … of … deductions — download the CSV for the full list."*
## Spend Limits
The **Spend Limits** sub-tab caps spend by policy, independent of the balance:
> *"Cap inference spend with any combination of a daily, monthly, or lifetime limit. Limits apply across every API key and connected agent. The prepaid balance still applies on top."*
### Account Limits

*Figure 5: Account Limits: daily and monthly limits, and the collapsed Lifetime hard cap (advanced) section*
*"Limits that apply to this entire account (this location/facility), across every API key and connected agent."*
| Limit | Reset behaviour |
|-------|-----------------|
| **Daily limit** | *"Resets every day at midnight, facility local time."* Quick presets: $25 / $50 / $100 / $250. |
| **Monthly limit** | *"Resets on the 1st of each month, facility local time."* Quick presets: $50 / $100 / $250 / $500 / $1000. |
| **Total (hard cap)** | *"Lifetime ceiling. Counts spend since the last reset."* Never resets on its own. |
Enable any combination, set the amounts, and click **Save changes** (*"Spend limits saved."*). While a limit is active, the module shows progress toward it and projections such as *"On track to reach this limit around …"*, *"On track to reach this limit this period."* or *"Projected … by the end of this period."* (the Overview tab's Budget usage card says *"…today."* / *"…this month."*) When a limit is hit you'll see *"Limit reached for this period."* and requests are blocked with a *budget exceeded* error until the window resets (daily/monthly) or you raise/reset the limit.
### Lifetime hard cap (advanced)
Expand **Lifetime hard cap (advanced)** to manage the total cap:

*Figure 6: Lifetime hard cap (advanced) expanded, with Reset hard cap and the reset history*
- The counter tracks spend **since the last reset** — a brand-new facility counts from its first request.
- **Reset hard cap** — *"Reset the counter to $0 without changing the cap. The closed period is saved below."* / *"Starts the lifetime counter over from $0. The cap amount stays the same."* The confirmation shows *"The current period — spent … of … — is saved to the reset history."*
- **Reset history** records each closed period (**Reset on**, **Cap**, **Spent**). *"No resets yet."* until the first reset.
Use the hard cap for "never spend more than X without a human deciding to continue" — when it's reached, spending stays blocked until someone deliberately resets the counter or raises the cap.
### Per-service spend limits

*Figure 7: Per-service spend limits: set a tighter cap on an individual connected service*
> *"Override the account limits for a specific connected service."*
The info hint adds: *"Set a tighter cap on an individual service. The account limits below still apply on top."* Services are your connected AI Agents and Workflows.
Open a service's **More actions** (⋯) menu and choose **Spend Limits**; **Service Configuration** opens its full configuration drawer instead. The spend-limits dialog is titled **Set agent spend limits** for an AI Agent or **Set workflow spend limits** for a workflow; the limits (daily / monthly / lifetime) work the same way as account limits but count only that service's spend. For rate limits, model restrictions, and guardrails, follow the **Configure rate limits, guardrails and more in Service Access & API Keys** link — see [Connected Services](https://docs.performancehub.co/module-docs/model-router/ai-agents).
## What happens at zero balance
When the available balance reaches $0, inference is blocked with an *insufficient credits* error and the module warns: *"Your balance is depleted. Inference requests will be blocked until you top up."* AI Agents show a credits-required state instead of their chat. Model listing keeps working. Top up (or let auto top-up run) and requests resume within moments.
Source: https://docs.performancehub.co/module-docs/model-router/budgets-and-topups
---
# Activity & logs
Two tabs cover monitoring: **Activity** (aggregated analytics) and **Logs** (the per-request audit trail). Both cover every API key and connected service, and both record **metadata only — prompt and response content is never stored**.
## The Activity tab
Activity has four views — **Overview**, **Trends**, **Performance**, **Explore** — plus an **Export** button. All views share the header controls: filter by **Key / agent**, filter by **Model**, and a **Range** picker (Last 24 hours, Last 7/30/90 days, Year, All time, or Custom range).
### Overview

*Figure 1: The Activity header (view tabs, filters, range and Export) and the KPI cards*
The KPI cards, each with a comparison against the previous period (*"vs prev"* / *"No prior data"*):
| KPI | Meaning |
|-----|---------|
| **Spend this period** | *"Total spend over the selected date range."* — with the average cost *per request*. |
| **Requests** | Request count. |
| **Token volume** | Total tokens. |
| **Error rate** | *"Share of requests in this period that returned an error."* |

*Figure 2: Top API keys & agents, and Top sources*
**Top API keys & agents** ranks the keys and connected services by spend, and **Top sources** splits usage between API keys, agents and workflows.
Below these, three charts break usage down by model and by token type.

*Figure 3: Usage by model: spend over time for each model*

*Figure 4: Request volume by model*

*Figure 5: The Token breakdown (prompt, completion and reasoning tokens)*
Where prompt caching applies, a **Prompt token caching** card shows the **Cache hit rate** — *"Share of prompt tokens served from the provider prompt cache."* — and cached vs uncached token volumes.

*Figure 6: Prompt token caching*
### Trends

*Figure 7: Trends: models over time with a Trending list; the metric switch (Tokens, Spend, Requests) is at the top right*
Trends shows movement: each model (and each key/agent, in its own section) charted over time, with a **Trending** list of risers — including a **New** badge for entries with no usage in the previous period.
### Performance

*Figure 8: Performance: latency, throughput and error KPIs, with Average latency over time and Errors over time*
| KPI | Meaning |
|-----|---------|
| **Median latency** | *"Median (p50) end-to-end request duration over the selected range."* |
| **p95 latency** | *"95th-percentile request duration — the slow tail most users feel."* |
| **Avg time to first token** | *"Average delay before the first streamed token (streaming requests only)."* |
| **Throughput** | *"Average output generation speed across requests."* (tok/s) |
| **Error rate** / **Total errors** | Errors in the selected period. |

*Figure 9: Reliability & performance by model, with Top failing models and Top failing keys & agents*
The per-model table (requests, error rate, average and p95 latency, throughput, spend) is the fastest way to spot a misbehaving model; the failing-entities lists point straight at what to investigate. With no failures you'll see *"No errors in this period"*.
### Explore

*Figure 10: Explore: a pivot over your usage with Metric, Group by, Then by and Show top*
Explore is a free-form pivot: choose a **Metric** (e.g. Spend), **Group by** (Model, API key / agent, Source, Provider, Status), optionally nest with **Then by**, and cap the list with **Show top**. The result is a ranked breakdown with **Value** and **% of Total**. Row actions let you **Apply filters based on this model** (or key/agent) and jump to **View in logs**.
### Exporting activity data
Click **Export** to open the export dialog:

*Figure 11: Export activity data: what to export, date range, filters and columns*
- **What to export:** **Raw requests** (one CSV row per request) or a summary — **Summary by model**, **Summary by key & agent**, **Summary by source**, **Summary by period**.
- **Date range and filters:** the current view's range and filters carry over and can be adjusted. The dialog shows the approximate row count (*"approx. … requests"*); raw exports are capped at 25,000 rows (*"export is capped at 25,000"* appears when the range exceeds it).
- **Columns:** for raw exports, pick from Time (UTC), Request ID, Model, Provider, Status, Key / agent, Key hash, Source, End user, Prompt/Completion/Total/Cached/Reasoning tokens, Spend (USD), Duration (ms), TTFT (ms), Tokens/sec, Cache hit, Zero completion, and IP — with **Select all** / **Clear all**.
## The Logs tab
The Logs tab is the request-by-request audit trail.

*Figure 12: Logs: search, filters, live refresh and the request table*
- **Search** (*"Search request ID, key, model…"*) plus filters for **Status** (Success / Failed / All), **Key / agent**, **Model**, and **Provider**.
- **Range** presets: Last 15 minutes, Last hour, Last 24 hours (the default), Last 7 days and Last 30 days, or pick custom dates.
- **Live** sits next to the range and refreshes the table automatically: choose **Off**, **10s**, **30s** (the default) or **60s**. A countdown shows when the next refresh runs, and **Refresh now** forces one. Live is hidden when a custom range ends in the past.

*Figure 13: The Live control: Off, 10s, 30s or 60s refresh, next to the range picker*
- The **histogram** shows request volume over the range (errors highlighted); drag-select a region of it to zoom the table to that window (*"Showing logs for …"*, with **Clear selection**).
- **Export** opens **Export request logs**: choose raw requests or a summary, the date range, filters (Key / agent, Model, Provider, Status — plus your current search text) and, for raw exports, the columns.
For summaries, the dialog notes: *"Summaries cover whole days, so a sub-day or graph selection is widened to full days."* and *"The search text is not applied to summaries."*
Each row shows date, request ID, model, provider, input and output tokens, agent / key, speed (tok/s), cost, and status (**success** / **failure**). A **No charge** badge marks failed or empty responses that cost nothing. A **cached** badge on the input count shows tokens served from the prompt cache, and the footer shows *"Showing … of …"*. An empty result shows *"No requests yet."* or *"No requests match your filters."*
### Request details
Click a row to open the **Request details** drawer:

*Figure 14: Request details: metadata, token counts and cost*
| Section | Fields |
|---------|--------|
| **Request** | Request ID (copyable), Time, Status, Model (the model that served the request), Requested model (e.g. `auto` when the Auto Router chose the model), Provider, IP address, Cache hit, Latency, Time to first token, and API base (the upstream host) when recorded. |
| **Tokens** | Prompt tokens, Completion tokens, Total tokens, Speed — plus Cached tokens (read/write) and Reasoning tokens when present. |
| **Cost** | The exact cost of the request. |
| **Source** | **Key / Agent** (which API key or agent made the call) and **End user** (the end-user identifier, if the caller supplied one). |
There is deliberately no prompt or response content anywhere in the logs.
Source: https://docs.performancehub.co/module-docs/model-router/activity-and-logs
---
# Reference
Exhaustive tables for **Model Router**. For concepts and task guides, start from the [overview](https://docs.performancehub.co/module-docs/model-router/overview).
## Module
| Item | Value |
|------|-------|
| Menu location | Main menu → **Model Router**, in the **AI Modules & Agents** group |
| **Who can access** | Organisation Administrators only (Technology Stack facilities). |
| Scope | Per facility (select the facility in the main toolbar) |
| Currency | USD |
## Top-ups
| Setting | Value |
|---------|-------|
| Minimum top-up | $10 |
| Maximum single top-up | $5,000 |
| Presets | $25, $50, $100, $250, $500, $1000 |
| Payment method | The facility's stored billing card |
| Balance credit timing | Instant |
| Receipt | Receipt number + downloadable PDF per top-up; also emailed to the facility's billing contacts |
## Auto top-up
| Setting | Value |
|---------|-------|
| Trigger | Balance falls below your **When balance is below** threshold |
| Recharge amount | Your **Top up by** amount (minimum $10) |
| Check cadence | Roughly every 15 minutes |
| Failure handling | Banner shown for: no card on file, card requires authentication, card declined |
## Usage & reconciliation
| Item | Value |
|------|-------|
| Usage reconciliation | Every 15 minutes against the prepaid balance; shown in **Monthly usage**, grouped by calendar month (UTC) |
| Available balance | Live: credited total minus live spend (not just the reconciled ledger) |
| Estimated runway | Average daily spend this month projected against the balance; shown as *0 days*, *1 day*, *N days*, *365+ days*, or *Not enough data* |
## Account spend limits
| Limit | Reset | Presets |
|-------|-------|---------|
| Daily limit | Every day at midnight, facility local time | $25, $50, $100, $250 |
| Monthly limit | The 1st of each month, facility local time | $50, $100, $250, $500, $1000 |
| Total (hard cap) | Manual reset only (**Reset hard cap** — counter to $0, cap unchanged, closed period saved to the reset history) | — |
Any combination may be active. Limits apply across every API key and connected service combined; the prepaid balance still applies on top. Breaches block requests with a 402 `budget_exceeded` error until the window resets.
## Per-key / per-service settings
| Setting | Default | Notes |
|---------|---------|-------|
| Label | empty | Optional; used to attribute usage in Activity and Logs |
| Connection type (API keys only) | API key | **API key** stays in the API keys list; **External agent** appears under Connected Services |
| Daily spend limit | off | Resets at the start of each day, facility timezone |
| Monthly spend limit | off | Resets on the 1st of each month, facility timezone |
| Lifetime cap | off | Total spend over the lifetime of the key or service; never resets |
| Requests / min | Unlimited | Exceeding returns HTTP 429 |
| Tokens / min | Unlimited | Exceeding returns HTTP 429 |
| Restrict models | off (all facility-enabled models) | Only the selected models usable with this key or service |
| Override facility guardrails | off (inherits facility) | When on, the key's or service's own selection applies — including "none" |
## API key statuses & actions
| Status | Meaning |
|--------|---------|
| **Active** | The key works. |
| **Provisioning…** | Briefly shown while a new key is being set up. |
| **Disabled** | The key is revoked (the row is dimmed); it can be re-activated. |
| Action | Reversible? | Effect |
|--------|-------------|--------|
| **Edit** | — | Change label, limits, models, guardrails. |
| **Rotate** | No | New secret issued (shown once); old secret stops working immediately. |
| **Revoke** | Yes (**Re-activate**) | Key disabled immediately. |
| **Delete** | No | Key permanently removed. |
## Models
| Item | Value |
|------|-------|
| Default | All catalog models enabled |
| Automatically enable new models | When on, models newly added to the catalog are turned on for the facility automatically |
| Constraint | *"At least one model must stay enabled."* |
| Quick presets | All models, Frontier, Western providers, Open weights, Budget (input price ≤ $1.00 / 1M tokens) |
| Filters | Status (Any/Enabled/Disabled), Region (United States/Europe/China), Capability (Vision, Audio, PDF input, Reasoning) |
| Sort | Recommended, Name, Price, Context size — ascending/descending |
| Per-model info | Context window, input & output price per 1M tokens, capability icons (text, image, audio, PDF), reasoning setting |
## Auto Router
| Tier | Description |
|------|-------------|
| **Simple** | Short, straightforward prompts |
| **Medium** | Everyday tasks |
| **Complex** | Hard, multi-step work |
| **Reasoning** | Deep reasoning & analysis |
- Model ID: `auto`; *Usage-priced* — billed at the rate of the model the router picks per request.
- Per-facility routing editable via **Edit routing** (only enabled models selectable; every tier must have a model); **Reset to default** restores the standard map.
- Logs/Activity show the resolved model; the request drawer shows `auto` as **Requested model**.
## Guardrails
| Guardrail | Action on match |
|-----------|-----------------|
| PII masking | Masks personal data in the prompt; request continues |
| Secret redaction | Strips keys/tokens/secrets from the prompt; request continues |
| Prompt-injection shield | Blocks the request (403 `content_policy_violation`) |
| Template | Enables |
|----------|---------|
| None | Nothing (default) |
| PII protection | PII masking |
| Secret redaction | Secret redaction |
| Injection shield | Prompt-injection shield |
| Strict | All three |
## Content filters
| Item | Value |
|------|-------|
| Maximum filters per facility | 25 |
| Maximum pattern length | 300 characters |
| Actions | **Redact** (rewrite matches to `[REDACTED]`) or **Block** (reject with 403) |
| Pattern safety | Unsafe regular expressions (e.g. patterns prone to catastrophic backtracking) and patterns over 300 characters are dropped and not applied |
| Inheritance | Facility-wide; keys and services with the guardrail override use their own list |
## Transaction history
The transaction history lists top-ups and balance adjustments only. Usage is summarised in Monthly usage.
| Type | Meaning |
|------|---------|
| **Top-up** | Card charge crediting the balance |
| **Refund** | Refunded charge |
| **Adjustment** | Manual balance correction |
| Item | Value |
|------|-------|
| Columns | Date, Type, Amount, Balance after, Receipt |
| Bulk receipts | Tick rows, then **Download receipts (n)** |
## Monthly usage
| Item | Value |
|------|-------|
| Grouping | Calendar month (UTC); the current month is marked **Month to date** |
| Reconciliation | Every 15 minutes against the prepaid balance |
| Columns | Month, Usage, Deductions, Downloads |
| Deduction type | **Usage charge** (reconciled inference spend), listed when you expand a month |
| Downloads per month | **Download usage statement (PDF)** and **Download usage detail (CSV)** |
| Status | Usage statements, not invoices; only card top-ups have receipts |
## Activity & logs
| Item | Value |
|------|-------|
| Activity views | Overview, Trends, Performance, Explore |
| Range presets | Last 24 hours, Last 7 days, Last 30 days, Last 90 days, Year, All time, Custom range |
| Logs range presets | Last 15 minutes, Last hour, Last 24 hours (default), Last 7 days, Last 30 days, custom; Live refresh Off / 10s / 30s (default) / 60s |
| Explore group-by options | Model, API key / agent, Source, Provider, Status |
| Raw CSV export cap | 25,000 rows |
| Export types | Raw requests; Summary by model / by key & agent / by source / by period (summaries cover whole days) |
| Content stored | Metadata only — never prompt or response content |
## API error responses
Base URL: shown in Settings → Service Access & API Keys → **Connecting to the API** (e.g. `https://app.performancehub.co/ai/v1`). Errors use the OpenAI envelope `{ "error": { "message", "type", "code" } }`.
| HTTP | `type` | `code` | Meaning |
|------|--------|--------|---------|
| 401 | `invalid_request_error` | `invalid_api_key` | Missing API key, or invalid connected-service credentials |
| 401 | `invalid_request_error` | — | Unknown or revoked API key (*"Invalid API key"*) |
| 402 | `insufficient_quota` | `insufficient_credits` | Prepaid balance depleted (or, for connected services, the facility is not enabled) |
| 402 | `insufficient_quota` | `budget_exceeded` | A spend limit reached (facility, key, or connected service); body includes `window`, `resetsAt`, `timezone`, `docsUrl` |
| 403 | `invalid_request_error` | — | *"Model Router is not enabled for this facility"* (API keys), or *"Facility has no provisioned inference key"* (the key has no backing inference key) |
| 403 | `invalid_request_error` | `content_policy_violation` | Blocked by a content filter or the prompt-injection shield |
| 429 | — | — | Requests/min or tokens/min rate limit exceeded |
| 502 | `server_error` | — | Upstream inference service unavailable (retry) |
An unexpected failure while checking the API key returns HTTP 500 *"Something went wrong"*; retry the request.
`GET /v1/models` remains available with a zero balance.
Source: https://docs.performancehub.co/module-docs/model-router/reference
---
# Troubleshooting
Work through the symptom that matches what you're seeing. **In almost every case, the Logs tab is the fastest diagnostic** — it shows each request's status and the failure pattern (one key? one model? everything?). See [Activity & logs](https://docs.performancehub.co/module-docs/model-router/activity-and-logs).
## Requests are blocked
### Every request fails with 402 `insufficient_credits`
**Cause:** the module is not enabled for this facility, or the prepaid balance is depleted (the Overview tab will show *"Your balance is depleted. Inference requests will be blocked until you top up."*). For API keys, a facility where Model Router isn't enabled returns 403 *"Model Router is not enabled for this facility"* instead.
**Fix:** enable Model Router from the **Welcome to Model Router** page (see [Getting started](https://docs.performancehub.co/module-docs/model-router/getting-started)), then top up under Budgets & Limits → Balance & Top-up. Requests resume within moments of the balance being credited. To prevent recurrence, enable auto top-up. See [Budgets & top-ups](https://docs.performancehub.co/module-docs/model-router/budgets-and-topups).
### Requests fail with 402 `budget_exceeded`
**Cause:** a spend limit was reached. The error message states which window (daily / monthly / lifetime) and — for daily and monthly — exactly when it resets in your facility's timezone. It may be the account-wide limit or a limit on the specific key or connected service (the message says *"This key's"* / *"This agent's"* when scoped).
**Fix:** either wait for the stated reset, raise the limit (Budgets & Limits → Spend Limits, or the key's or service's configuration), or — for the lifetime hard cap — use **Reset hard cap**. The Spend Limits page shows exactly which limits are at *"Limit reached"*.
### Requests fail with 403 `content_policy_violation`
**Cause:** the prompt matched a **block**-mode content filter or the prompt-injection shield.
**Fix:** review Settings → Guardrails. If one integration legitimately needs different rules, use its **Override facility guardrails** toggle rather than loosening the facility-wide policy. See [Guardrails](https://docs.performancehub.co/module-docs/model-router/guardrails).
### Requests fail with 429
**Cause:** the key or connected service exceeded its **Requests / min** or **Tokens / min** rate limit.
**Fix:** back off and retry, or raise the rate limits in the key or service configuration (blank = unlimited). See [API keys](https://docs.performancehub.co/module-docs/model-router/api-keys).
### A specific model returns errors, others work
**Cause:** the model may be disabled for your facility, excluded by the key's model restriction, or the provider may be having issues.
**Fix:** check Settings → Models (is it enabled?), the key's **Restrict models** list, and Activity → Performance → **Reliability & performance by model** for the model's error rate over time.
## API key problems
### 401 Unauthorized
**Cause:** with code `invalid_api_key` (*"Missing or invalid API key"*), the `Authorization: Bearer …` header is missing or malformed. With no code (*"Invalid API key"*), the key is unknown (mistyped, or an old secret after a **rotate**) or **revoked**.
**Fix:** confirm the header format; check the key's status in Settings → Service Access & API Keys. Re-activate a revoked key, or update your service to the new secret if the key was rotated.
### I lost the key secret
The secret is shown only once at creation and cannot be recovered. Use **Rotate** — a new secret is issued (shown once) and the key keeps its label, limits, and history. Update every service using the key: *"You will need to reconfigure any services still using the old key."*
### The key works but usage shows under the wrong name
Keys are identified by their **Label** in Activity and Logs. Edit the key and set a meaningful label; give each system its own key rather than sharing one.
## Agent problems
### An agent shows Offline
**Cause:** the device is powered off or has no connectivity — this is device connectivity, not a Model Router setting.
**Fix:** check the device's power and network. Its access configuration is unaffected and works again as soon as it reconnects.
### An agent stopped responding to AI requests
**Check, in order:**
1. **Balance** — a depleted balance blocks all agents (the agent shows a credits-required state). Top up.
2. **Per-service limits** — Settings → Service Access & API Keys → **Connected Services**: does the agent show *"Limit reached"*? Raise or wait out the limit.
3. **Account limits** — Budgets & Limits → Spend Limits.
4. **Logs** — filter by the agent to see the actual error on its requests.
## Balance & billing questions
### Spend figures look behind
The **Available balance** and **Spent today** figures are live. **Monthly usage** is reconciled against your balance every 15 minutes, so very recent spend can take a few minutes to appear there. The transaction history lists only top-ups and balance adjustments, so usage never shows in it.
### Auto top-up didn't run
Check the Balance & Top-up page for a banner: no card on file, card needs authentication, or card declined. Fix the card (**Update card** / **Edit payment details**) or top up manually. Auto top-up checks run roughly every 15 minutes, so a just-crossed threshold can take a few minutes to trigger.
### A top-up failed
*"Additional card authentication is required. Please try again."* — retry; your bank may prompt for verification. *"Top-up could not be completed."* — the charge was declined; check the card or try another amount. No credit is applied unless the charge succeeds.
### I was charged but the balance didn't move
Top-ups credit instantly — refresh the page. If the balance still doesn't reflect a successful charge, note the **Transaction ID** and **Payment reference** from the transaction's details drawer (or your card statement) and raise a support request.
## Logs & analytics questions
### A request I just made isn't in the Logs yet
Turn on **Live** refresh (or click **Refresh now**). Requests normally appear within moments; under load it can take slightly longer.
### Logs show a model I didn't request
If you sent `model: "auto"`, the logs show the model the Auto Router **resolved to** — open the request's details to see **Requested model: auto**. This is expected. See [Models](https://docs.performancehub.co/module-docs/model-router/models).
### Rows show "No charge"
Failed or empty responses that cost nothing are badged **No charge** — they're recorded for completeness but didn't consume balance.
### My CSV export is smaller than expected
Raw exports are capped at 25,000 rows — narrow the date range or use a summary export. Also note: *"Summaries cover whole days, so a sub-day or graph selection is widened to full days."*
Source: https://docs.performancehub.co/module-docs/model-router/troubleshooting
---
# Common questions
## Access
**Who can use Model Router?**
Organisation Administrators, at Technology Stack facilities. Open it from the main menu, in the **AI Modules & Agents** group.
## Billing & credits
**Is this a subscription?**
No — it's prepaid. You top up a USD balance and usage draws it down at each model's per-token price. There's no recurring AI charge beyond what you use.
**What happens when the balance runs out?**
Inference requests are blocked with an *insufficient credits* error until you top up. Nothing is lost — keys, connected services, settings, and history all remain — and requests resume within moments of a top-up. Enable auto top-up to avoid interruptions.
**Can I get a refund of unused balance?**
Raise a support request. Refunds appear in the transaction history as their own type.
**Do I get receipts?**
Yes — every top-up has a receipt number and a downloadable PDF in the transaction's details drawer, and the receipt is emailed to your facility's billing contacts. To download several at once, tick the rows in the transaction history and click **Download receipts (n)**. Usage isn't receipted: **Monthly usage** offers a usage statement (PDF) and usage detail (CSV) for each month instead. These are usage statements, not invoices. Model Router receipts also appear with the facility's other billing documents in Billing & Cost Management's [Invoices & Receipts](https://docs.performancehub.co/module-docs/billing-cost-management/invoices-receipts).
**Why does "Spent today" differ from Monthly usage?**
**Spent today** and the available balance are live, while **Monthly usage** is reconciled against your balance every 15 minutes. They converge on the next reconciliation. The transaction history lists only top-ups and balance adjustments, so usage doesn't appear there at all.
**How is the Auto Router priced?**
It has no price of its own — you pay the rate of the model it picks for each request. The logs show which model that was.
## Privacy & data
**Are my prompts or the AI's responses stored?**
No. Activity and Logs record metadata only — model, tokens, cost, latency, status, caller. Prompt and response content is never stored by Performance Hub.
**Can I stop personal data reaching the models?**
Yes — enable **PII masking** under Settings → Guardrails, and add custom content filters for anything domain-specific. See [Guardrails](https://docs.performancehub.co/module-docs/model-router/guardrails).
**Can I restrict which providers my data goes to?**
Yes — the Models page has one-click presets (e.g. **Western providers**, described as *"data-sovereignty friendly"*) and per-model toggles. Keys and connected services can be restricted further. See [Models](https://docs.performancehub.co/module-docs/model-router/models).
## API keys & integrations
**What software can I connect?**
Anything that speaks the OpenAI API and lets you set a custom base URL and API key (BYOK) — the OpenAI SDKs, most AI frameworks, and many off-the-shelf tools. See [Connecting your application](https://docs.performancehub.co/module-docs/model-router/connecting-your-application).
**Can I see the key secret again after creating it?**
No — it's shown exactly once. If it's lost, **Rotate** the key for a new secret.
**How many keys should I create?**
One per system. Labels make each system's usage visible in Activity and Logs, and a compromised key can be rotated without touching anything else.
**What's the difference between revoking and deleting a key?**
Revoke disables the key immediately but reversibly (**Re-activate** restores it). Delete is permanent.
**Can one key be limited to specific models?**
Yes — **Restrict models for this key** in the key's configuration. The same applies to each connected service.
## AI Agents and Workflows
**Do my AI Agents need an API key?**
No. Agents paired with your facility connect automatically with per-device credentials that Performance Hub manages. They appear under Settings → Service Access & API Keys → **Connected Services** with their own spend and controls.
**Can Workflows use Model Router?**
Yes. Workflows can connect as a service, and then appear under **Connected Services** alongside your AI Agents.
**Can I cap what a single agent or workflow spends?**
Yes — per-service daily, monthly, and lifetime limits, plus rate limits, model restrictions, and guardrail overrides. See [Connected Services](https://docs.performancehub.co/module-docs/model-router/ai-agents).
## Models & routing
**Which model should my integration use?**
`auto` (the Auto Router) is the recommended default — it routes each request to a complexity-appropriate model, so simple work goes to fast, cheap models and hard tasks get the strongest ones. Or use any specific model ID enabled for your facility.
**Why do my logs show a different model than I requested?**
You requested `auto` — the logs show the model the router resolved to, and the request drawer records `auto` as **Requested model**. Expected behaviour.
**Can I change what `auto` routes to?**
Yes — **Edit routing** on the Auto Router card lets you pick the model for each complexity tier for your facility, with **Reset to default** available. See [Models](https://docs.performancehub.co/module-docs/model-router/models).
**A model I want isn't in the catalog.**
Use the **Raise a support request** link on the Models page: *"Want to see a model added? Raise a support request and we'll add it if it's generally available."*
## Limits
**What's the difference between the balance and a spend limit?**
The balance is money you've credited; spend limits are policy caps you choose. Either can block requests: an empty balance blocks until you top up; a hit limit blocks until its window resets (or you change it). See [How it works](https://docs.performancehub.co/module-docs/model-router/how-it-works).
**When exactly do daily and monthly limits reset?**
Daily at midnight and monthly on the 1st — both in the **facility's local timezone**. The *budget exceeded* error message states the precise reset time.
**Does the lifetime hard cap ever reset itself?**
No. It counts spend since its last manual reset. **Reset hard cap** starts the counter over from $0 (the cap amount is unchanged) and saves the closed period to the reset history.
**Do per-key limits and account limits stack?**
Yes — both apply. A request must pass the account limits and the calling key's or service's own limits (and the balance) to run.
Source: https://docs.performancehub.co/module-docs/model-router/common-questions
---
# Pricing & Directories
This section is the reference shelf for the platform: what usage costs, and what we
support. The pricing pages publish the **public list price** for everything billed on
measured usage, with no minimums. The directories are live, searchable catalogues of what
the platform works with, from AI models and app integrations to CCTV cameras.
## Pricing
Each pricing area explains exactly how its charges are calculated, you can check any
figure against your own spend in the dashboard, and nothing is hidden behind a sales call.
### Platform pricing
> **Standard rates:** Performance Hub's rates for devices and platform services, from Display
> Screens and Access Control to CCTV / VSaaS, Storage and Agents, are on
> [Platform pricing](https://docs.performancehub.co/module-docs/platform-pricing/rates). The rates that apply to your account
> are on the [Pricing](https://docs.performancehub.co/module-docs/billing-cost-management/cost-breakdown-pricing#pricing) tab.
### AI models
## Directories
Searchable catalogues of what the platform supports, kept current automatically.
## Public pricing and enterprise agreements
Every price in this section is the **public list price**, which is what a standard account
pays. Enterprise accounts may have different pricing depending on the customer, committed
volume, and deployment scenario, including private or on-premise deployments where the
underlying cost structure is different.
If your organisation has an enterprise agreement, the rates in that agreement take
precedence over the list prices published here. The dashboard always shows the rates that
apply to your facility, and your invoices are calculated from those rates.
## Where to see your own costs
Published prices tell you the rate. To see what you have actually spent, use the dashboard:
- **Activity** for spend over time, broken down by model, key, and agent. See
[Activity and logs](https://docs.performancehub.co/module-docs/model-router/activity-and-logs).
- **Balance and transactions** for top-ups and deductions. See
[Budgets and top-ups](https://docs.performancehub.co/module-docs/model-router/budgets-and-topups).
- **Billing & Cost Management** for a facility's costs, invoices and the rates that apply
to your account. See [Cost Breakdown & Pricing](https://docs.performancehub.co/module-docs/billing-cost-management/cost-breakdown-pricing).
Source: https://docs.performancehub.co/module-docs/platform-pricing/overview
---
# Model Catalog
The **Model Router** is the single endpoint your applications and agents call to reach every
AI model Performance Hub offers. You use one API key and one base URL; we handle the
upstream provider accounts, rate limits, and billing.
This page is the live price list. Prices are per 1 million tokens in US dollars and
**already include the 5 percent platform fee**, so the figures below are exactly what your
facility is billed. The catalog refreshes automatically several times a day and this page
reads it live.
## Reading the table
| Column | What it means |
|--------|---------------|
| **Context** | The largest amount of input the model accepts in one request, in tokens |
| **Input / 1M** | Retail price per million input tokens |
| **Output / 1M** | Retail price per million output tokens, usually several times the input price |
| **Intelligence** | Artificial Analysis intelligence index, 0 to 100. A third-party composite benchmark score, shown as a dash for models they do not track |
| **Value** | Intelligence divided by blended price. Higher means more capability per dollar |
Blended price is `(3 x input + output) / 4`, which approximates a typical workload where
input tokens outnumber output tokens by roughly three to one.
The badges on each model show its capabilities: **Vision** accepts image input,
**Reasoning** produces reasoning tokens, and **Frontier** marks a flagship model from a
major lab. Provider groups show the region their models are served from, which matters if
you have data-sovereignty requirements.
## How you are charged
Model usage is billed **per token**, at the rate published above for the model that served
the request. Tokens are counted separately for input and output, because providers price
them differently, and output is usually several times more expensive than input.
| Component | Meaning |
|-----------|---------|
| **Input tokens** | Everything sent to the model: your prompt, system message, conversation history, tool definitions, and any image or document content |
| **Output tokens** | Everything the model generates, including reasoning tokens on models that produce them |
| **Cache read** | Input tokens served from the provider's prompt cache, where supported. Much cheaper than fresh input |
| **Cache write** | The cost of placing tokens into the prompt cache, where supported. Slightly more than fresh input |
A charge is calculated as:
```text
cost = (input tokens / 1,000,000) x input price
+ (output tokens / 1,000,000) x output price
```
Charges are deducted from your prepaid balance as requests complete. There is no monthly
minimum and no charge for models you do not use.
## The 5 percent platform fee
We buy model capacity from upstream providers and resell it at **the upstream price plus a
flat 5 percent**. That 5 percent is the whole of our margin on model usage. It covers what
sits around the model call: the routing layer, API key issuing and rotation, per-key and
per-agent limits, guardrails, request logging, usage metering, billing and top-ups, and
support. It does not vary by model, by provider, or by how much you spend.
### A worked example
Take a model published at **$5.25 per 1M input tokens** and **$26.25 per 1M output tokens**:
| Step | Input | Output |
|------|-------|--------|
| Upstream provider price per 1M | $5.00 | $25.00 |
| Platform fee at 5 percent | $0.25 | $1.25 |
| **Published price per 1M** | **$5.25** | **$26.25** |
A request using 12,000 input tokens and 800 output tokens costs:
```text
input: 12,000 / 1,000,000 x $5.25 = $0.0630
output: 800 / 1,000,000 x $26.25 = $0.0210
total = $0.0840
```
The same request on a budget model published at $0.032 in and $0.137 out costs about
$0.0005, which is why model choice matters far more than the fee.
## How the Auto Router is charged
The Auto Router (`auto`, shown at the top of this page) has no price of its own. It
inspects each request, routes it to a model based on complexity, and **you are billed at
that model's published rate**. Simple prompts land on fast, cheap models; only genuinely
hard work reaches the expensive ones.
Each facility can remap the tiers to its own preferred models, which changes what its
`auto` requests cost. See [Models](https://docs.performancehub.co/module-docs/model-router/models) for how to edit
the routing.
## When prices change
Model prices are refreshed automatically from upstream providers several times a day. When
a provider changes a price, the published price follows it, with the same 5 percent applied.
We do not issue price change notices for upstream movements, so if you are budgeting
tightly, check this page rather than a figure you noted previously.
New models are added when they become generally available. Models are withdrawn when the
upstream provider retires them.
## What is not charged
- Enabling or disabling models for your facility.
- Creating, rotating, or revoking API keys.
- Guardrail evaluation on your requests.
- Activity, logs, and usage exports.
- Failed requests that never reached a model, such as an authentication error or a request
blocked by a guardrail or a spend limit. If a request reaches the model and the model
responds, it is billed, even if your application discards the response.
## Choosing a model
- **Do not start with the most expensive model.** The gap between a budget model and a
frontier model can be a factor of a hundred in price, and most everyday tasks do not need
the frontier.
- **Use the Auto Router** if you do not want to choose per request. It sends simple work to
cheap models and reserves the expensive ones for hard tasks.
- **Watch the output price.** Output is where cost accumulates on generative workloads.
- **Check the value score** for a shortlist, then compare candidates on your own prompts.
Benchmarks are a starting point, not a verdict.
To compare models visually, see the
[Model Rankings](https://docs.performancehub.co/module-docs/platform-pricing/model-router/model-rankings). To control
which of these models your facility, keys, and agents can use, see
[Models](https://docs.performancehub.co/module-docs/model-router/models).
## Public pricing and enterprise agreements
These are public list prices for standard accounts. Enterprise customers may have different
rates depending on committed volume and deployment scenario, and those rates take
precedence. See [Pricing & Directories](https://docs.performancehub.co/module-docs/platform-pricing/overview) for more.
Source: https://docs.performancehub.co/module-docs/platform-pricing/model-router/model-catalog
---
# Model Rankings
How the models on the Model Router compare on capability, price, and the trade-off between
them. Every figure uses live retail prices, with the 5 percent platform fee already
included, so the comparisons reflect what you would actually pay.
Intelligence scores come from the [Artificial Analysis](https://artificialanalysis.ai)
intelligence index, an independent composite benchmark scored from 0 to 100. Models they do
not track are left out of the intelligence-based charts rather than scored as zero.
## How to use these charts
Benchmarks measure general capability on standard tasks. Your workload is not a standard
task. Use the charts to build a shortlist of two or three candidates at the price point you
can afford, then test them on your own prompts and compare the results.
A few patterns worth knowing:
- **Price and intelligence are only loosely related.** Several mid-priced models score close
to models costing ten times more.
- **Value scores favour cheap models.** A model with a modest score at a very low price can
top the value leaderboard while still being the wrong choice for hard work.
- **Provider averages hide wide ranges.** A provider's cheapest and most expensive models can
differ by two orders of magnitude, so the average is a rough orientation only.
For the full price list and how charges are calculated, see the
[Model Catalog](https://docs.performancehub.co/module-docs/platform-pricing/model-router/model-catalog).
Source: https://docs.performancehub.co/module-docs/platform-pricing/model-router/model-rankings
---
# Platform pricing
These are Performance Hub's standard rates for devices and platform services, grouped by
platform. They are the list prices a standard account pays, whether the devices are display
screens in a hotel lobby, door controllers on a college campus or cameras in a retail store.
The rates that apply to your own account are on the **Pricing** tab in Billing & Cost
Management. See [Cost Breakdown & Pricing](https://docs.performancehub.co/module-docs/billing-cost-management/cost-breakdown-pricing#pricing).
## Rates
Prices shown are standard rates in USD, excluding taxes. Pricing may differ by account or when buying through a channel partner. Check the Pricing tab in your account for your rates.
Performance Hub Inc reserves the right to change these prices from time to time.
Rates as of 29 October 2026
### Display Screens
Screens that show your content, from a reception lobby to a café menu board.
| Service | Rate | Billed |
| --- | --- | --- |
| Display Screen | US$19.00 | per device / month |
### Access Control
Door and gate controllers for Door & Gate Access.
| Service | Rate | Billed |
| --- | --- | --- |
| Door / Gate Controller | US$29.00 | per device / month |
### Duress
Duress and panic buttons for staff safety.
| Service | Rate | Billed |
| --- | --- | --- |
| Duress / Panic Button | US$2.00 | per device / month |
### CCTV / VSaaS
Edge Processors for CCTV, and the camera features they run.
| Service | Rate | Billed |
| --- | --- | --- |
| Edge Processor (CCTV / VSaaS) | US$49.00 | per device / month |
| Network Camera — Standard Recording | Included | included with Edge Processor |
| Network Camera — Audio Transcriptions | US$9.00 | per feed / month |
| Network Camera — People Tracking & Identification | US$18.00 | per feed / month |
### Storage
Cloud Object Storage.
| Service | Rate | Billed |
| --- | --- | --- |
| Cloud Object Storage (Storage is billed for a minimum of 30 days. This applies unless your account has custom pricing, which can be set for larger accounts and channel partners.) | US$0.00977 | per GB / month |
### Agents
Agents on a physical device, and Cloud Agent instances by size.
| Service | Rate | Billed |
| --- | --- | --- |
| Agent (Physical Device) | US$49.00 | per device / month |
#### Cloud Agent sizes
| Size | vCPU | RAM | Disk | Per hour | Est. per month |
| --- | --- | --- | --- | --- | --- |
| Small | 2 | 4 GB | 64 GB | US$0.057 | US$39.77 |
| Medium | 4 | 8 GB | 128 GB | US$0.085 | US$59.12 |
| Large | 4 | 16 GB | 256 GB | US$0.116 | US$79.60 |
| 4 vCPU / 32 GB RAM | 4 | 32 GB | 512 GB | US$0.288 | US$195.71 |
| 8 vCPU / 32 GB RAM | 8 | 32 GB | 512 GB | US$0.354 | US$239.71 |
| 8 vCPU / 64 GB RAM | 8 | 64 GB | 1,024 GB | US$0.609 | US$411.43 |
| 16 vCPU / 128 GB RAM | 16 | 128 GB | 2,048 GB | US$1.039 | US$699.86 |
| 32 vCPU / 192 GB RAM | 32 | 192 GB | 2,048 GB | US$1.595 | US$1,073.86 |
| 64 vCPU / 256 GB RAM | 64 | 256 GB | 2,048 GB | US$1.873 | US$1,260.86 |
Each size includes the vCPU, RAM and disk shown in its row.
Cloud Agent Instances are billed per hour, rounded up to the whole hour, with a minimum of 1 hour per launch.
Billing runs from launch until you delete the agent. There is no stop or pause, so an agent is billed for every hour it exists, whether or not it is busy. To stop charges, delete the agent.
“Est. per month” is an estimate for an agent running all month. Actual charges depend on the hours the agent exists.
Data transfer in and out (network ingress and egress) is included in the Cloud Agent price, subject to fair use, with no separate data-transfer charge. The price you see for a size when you launch covers its disk and network.
Prices can vary slightly by region. The launch wizard shows the exact price for your agent.
Backups are on by default and are billed as a separate “AI Agent Backup Storage” line at the Cloud Object Storage rate.
Model Router usage is billed separately, from its own prepaid credits.
There is no setup fee, minimum term or monthly minimum.
No GPU sizes are offered.
## How these rates are billed
- **Device charges** bill the full month, whenever in the month a device is added or removed.
- **Cameras behind an Edge Processor** record at no extra charge, so standard recording shows
as "Included". A camera bills only for its AI features, and a camera with more than one AI
feature bills only the highest one.
- **Cloud Object Storage** and **Cloud Agent hours** build up day by day, so they grow through
the month.
- **Model Router** is paid from its own prepaid balance, not the platform invoice. See
[Model Catalog](https://docs.performancehub.co/module-docs/platform-pricing/model-router/model-catalog) for the models and
their prices.
## Your account's rates
Every rate on this page is the **public list price**. Enterprise accounts may have different
pricing depending on the customer, committed volume and deployment scenario, including
private or on-premise deployments. If your organisation has an enterprise agreement, the rates
in that agreement take precedence over the list prices published here.
Performance Hub always shows the rates that apply to your facility, and your invoices are
calculated from those rates:
- **Billing & Cost Management** → **Pricing**: the rates for a facility that pays for itself.
See [Pricing](https://docs.performancehub.co/module-docs/billing-cost-management/cost-breakdown-pricing#pricing).
- **Organisation Settings** → **Billing**: the same **Pricing** tab for each facility your
organisation pays for. See [One facility's billing](https://docs.performancehub.co/module-docs/organisation-settings/billing-overview#one-facilitys-billing).
## Related pages
- [Cost Breakdown & Pricing](https://docs.performancehub.co/module-docs/billing-cost-management/cost-breakdown-pricing): read a month's usage, service by service
- [Overview & Facilities](https://docs.performancehub.co/module-docs/organisation-settings/billing-overview): your organisation's costs across every facility
- [Pricing & Directories](https://docs.performancehub.co/module-docs/platform-pricing/overview): every pricing area and directory
Source: https://docs.performancehub.co/module-docs/platform-pricing/rates
---
# App Directory
The Performance Hub AI Agent works inside the software your facility already uses. An
administrator connects apps to an agent from the agent's **Connected Apps** screen in
Performance Hub, and from that moment the agent can read from and act in those apps on
your behalf. Connections are made per agent, and an app can have several accounts
connected at once - for example, multiple inboxes or multiple calendars.
This page is the live directory of every app the agent can connect to. It refreshes
automatically twice a week.
## How the agent uses connected apps
Every connected app is exposed to the agent through **MCP (Model Context Protocol)**, the
open standard for connecting AI agents to tools. Each app publishes a set of tool
specifications - the operations the agent can perform, what each one does, and the access
it needs. The agent discovers these tools the moment an app is connected, and because
discovery happens over MCP, **new capabilities added to an integration reach your agent
automatically** - there is nothing to upgrade or reconfigure when an app gains new tools.
## Connection types
Every app in the directory connects in one of a few ways. The badge on each app tells you
which applies:
| Badge | What it means for you |
|-------|-----------------------|
| **Always included** | Nothing to connect. The service is on for every agent and can be switched off individually. See [Built-in Services](https://docs.performancehub.co/module-docs/platform-pricing/connected-apps/built-in-services). |
| **One-click connect** | A single sign-in from the agent's Connected Apps screen. Performance Hub manages the app registration, so there is nothing to set up first. Google Workspace and Microsoft 365 apps connect this way, individually or via the suite connect. |
| **Bring your own OAuth app** | You register an OAuth app with the vendor once (Performance Hub shows a step-by-step guide), then sign in through it. Required by vendors that do not allow shared registrations. |
| **API key / token** | Paste an API key or token from the app's own settings. Performance Hub walks you through where to find it. |
## How connecting works
1. An administrator opens the agent's **Connected Apps** screen in Performance Hub.
2. They pick an app and either sign in (OAuth) or paste a key, depending on the app's
connection type above.
3. The agent discovers the app's tools over MCP and can use them immediately - no
restart, no configuration.
A few things worth knowing:
- **Connections are private to one agent.** Connecting an app to one agent does not share
it with any other agent in your facility.
- **Multiple accounts per app are supported.** Connect two inboxes and the agent picks
which account to use for each action.
- **Built-in services need nothing.** They are available from the moment an agent is
created - see [Built-in Services](https://docs.performancehub.co/module-docs/platform-pricing/connected-apps/built-in-services).
- **Disconnecting is immediate.** Remove a connection and the agent loses access to that
account at once.
## What connecting costs
Connected apps are **included with your agent subscription at no extra charge**: there is
no per-integration fee and no charge per tool call to a connected app.
A **fair usage policy** applies. If an agent's usage of a connected app reaches the app's
rate limits, we may charge overage for the excess - and if that ever happens, **we notify
you automatically** so it never comes as a surprise on an invoice.
For how agent usage itself is billed, see
[Pricing & Directories](https://docs.performancehub.co/module-docs/platform-pricing/overview). To compare the AI models
that power agents, see the
[Model Catalog](https://docs.performancehub.co/module-docs/platform-pricing/model-router/model-catalog).
## Integration partners
Some integrations are provided through our partnership with Composio - you may see the
Composio name on sign-in and approval screens when connecting certain apps.
Source: https://docs.performancehub.co/module-docs/platform-pricing/connected-apps/app-directory
---
# Built-in Services
Built-in services are part of the platform itself. Every agent has them from the moment
it is created: there is nothing to connect, no account to sign in to, and no key to
paste. Each service can be switched off individually per agent from the agent's
**Connected Apps** screen in Performance Hub if you do not want it available.
Like connected apps, built-in services are exposed to the agent through **MCP (Model
Context Protocol)**: each service publishes tool specifications the agent discovers
automatically, and new capabilities reach every agent as soon as they ship. Where the
full specifications are published, you can expand them below.
## How they differ from connected apps
| | Built-in services | Connected apps |
|---|---|---|
| **Setup** | None - on for every agent | Connected per agent by an administrator |
| **Accounts** | No account involved | Uses your facility's own accounts |
| **Data access** | Public data and utilities only | Reads and acts in your own systems |
| **Switch off** | Per agent, per service | Disconnect at any time |
Browse everything the agent can connect to in the
[App Directory](https://docs.performancehub.co/module-docs/platform-pricing/connected-apps/app-directory).
## Integration partners
Some services are provided through our partnership with Composio - you may see the
Composio name on approval screens.
Source: https://docs.performancehub.co/module-docs/platform-pricing/connected-apps/built-in-services
---
# About the Camera Database
The [Camera Database](https://docs.performancehub.co/module-docs/cctv-vsaas/camera-database) is a curated collection of known stream URL configurations for CCTV cameras, maintained so you can find your camera, fill in your own credentials and IP address, and paste a working URL straight into [Device Management](https://docs.performancehub.co/module-docs/cctv-vsaas/cameras-and-devices) when adding a third party network camera to an Edge Processor.
## Where the data comes from
Entries are compiled from manufacturer documentation, verified customer deployments, and testing against real hardware where possible. Because most cameras from the same manufacturer share a single URL scheme, each manufacturer page lists a small number of **configurations** and maps models and whole model families onto them — a camera that isn't individually listed almost always works with its manufacturer's recommended configuration.
## Accuracy disclaimer
Camera firmware changes over time, and manufacturers occasionally move or rename stream paths between firmware versions. The paths in this database are correct for the hardware and firmware they were verified against, but they cannot be guaranteed for every unit in the field. Always use the built-in **Test Stream** button before adding a camera — the Edge Processor connects to the URL from the device itself and confirms it can read video, which catches a wrong path, wrong credentials, or an unreachable address immediately.
## Requesting additions
Know a camera we're missing, or found that a listed configuration is wrong for your model? [Contact support](https://docs.performancehub.co/support) with:
- The camera manufacturer and exact model number (from the label on the device, not the box).
- The full working stream URL, with your credentials and IP address replaced by placeholders (for example `rtsp://username:password@camera-ip:554/your/path`).
- The firmware version if you know it, since paths sometimes differ between versions.
Verified submissions are added to the database for everyone.
## Finding a stream URL yourself
If your camera isn't in the database and the generic paths don't connect, the URL is usually discoverable:
1. **The camera's web admin page.** Log in to the camera in a browser (its IP address, often on port 80). Look under network, streaming, or RTSP settings — many cameras display the exact stream URI, and most let you confirm the RTSP port and whether the stream service is enabled.
2. **The manual or manufacturer knowledge base.** Search for your model number together with "RTSP URL". Manufacturers commonly publish the scheme for their whole range in a single support article.
3. **ONVIF discovery.** If the camera supports ONVIF, a free tool such as ONVIF Device Manager (run from a PC on the same network) can query the camera and report its exact stream URIs — this works even for unbranded OEM hardware.
4. **Ask your installer or supplier.** For white-labelled cameras, the supplier usually knows which OEM platform the hardware is built on (Hikvision, Dahua, and Uniview platforms cover a large share of the market, and their paths are all in this database).
A few general rules worth knowing:
- Use the camera's **main (highest-quality) stream** — the Edge Processor handles quality adaptation for viewing, so there's no benefit to the substream.
- Give cameras **static IP addresses** (or DHCP reservations) so the URL never breaks.
- Passwords containing special characters must be **URL-encoded** in the URL (`@` becomes `%40`) — the database's URL builder does this for you automatically.
- The default RTSP port is **554**, but some cameras use a different port or share the HTTP port; check the camera's port settings if the connection is refused.
## Related pages
- [Camera Database](https://docs.performancehub.co/module-docs/cctv-vsaas/camera-database) — search the database
- [Cameras & devices](https://docs.performancehub.co/module-docs/cctv-vsaas/cameras-and-devices) — adding a network camera step by step
- [Troubleshooting](https://docs.performancehub.co/module-docs/cctv-vsaas/troubleshooting) — when a stream won't connect
Source: https://docs.performancehub.co/module-docs/cctv-vsaas/camera-database/about
---
# Connect Cursor to Performance Hub
> Instructions current as of 17 June 2026 (AEST). Vendor MCP support changes frequently - verify against the linked sources if something looks different.
Cursor supports MCP servers through `mcp.json`. Performance Hub runs a remote Streamable HTTP MCP server.
## Server details
```text
https://app.performancehub.co/mcp
```
## Global setup
Edit your global Cursor MCP config:
```text
~/.cursor/mcp.json
```
Add the Performance Hub server:
```json
{
"mcpServers": {
"performance-hub": {
"url": "https://app.performancehub.co/mcp",
"headers": {
"Authorization": "Bearer ${env:PH_MCP_API_KEY}"
}
}
}
}
```
Set the environment variable before launching Cursor:
```bash
export PH_MCP_API_KEY='phk_your_key_here'
```
## Project setup
To scope the server to a single project, create or edit:
```text
.cursor/mcp.json
```
Use the same config shape as above.
## Alternative header
Performance Hub also accepts the `x-mcp-api-key` header:
```json
{
"mcpServers": {
"performance-hub": {
"url": "https://app.performancehub.co/mcp",
"headers": {
"x-mcp-api-key": "${env:PH_MCP_API_KEY}"
}
}
}
}
```
## Verify
Reload Cursor after editing the config. Cursor should list the Performance Hub MCP tools available to your account.
If no tools appear, check that:
- Cursor was restarted or MCP was reloaded.
- The environment variable is available to Cursor.
- The key is active.
- Your Performance Hub user has access to the required modules and facilities.
## Sources
- Cursor MCP docs: https://cursor.com/docs/mcp
- Performance Hub MCP: https://app.performancehub.co/mcp
Source: https://docs.performancehub.co/ai-agents/clients/cursor
---
# Connect Hermes to Performance Hub
> Instructions current as of 17 June 2026 (AEST). Vendor MCP support changes frequently - verify against the linked sources if something looks different.
Hermes can use remote MCP servers through its `mcp_servers` configuration.
## Requirements
- Hermes installed and configured.
- A Performance Hub MCP key.
- The Performance Hub MCP server URL:
```text
https://app.performancehub.co/mcp
```
## Recommended setup
Store the key in Hermes' `.env` file rather than directly in `config.yaml`.
Find the env file path:
```bash
hermes config env-path
```
Add your key:
```bash
PH_MCP_API_KEY=phk_your_key_here
```
Then edit the Hermes config:
```bash
hermes config edit
```
Add the server:
```yaml
mcp_servers:
performance-hub:
url: "https://app.performancehub.co/mcp"
headers:
Authorization: "Bearer ${PH_MCP_API_KEY}"
timeout: 180
connect_timeout: 30
```
Hermes resolves `${PH_MCP_API_KEY}` from the environment loaded at startup.
## Alternative header
Performance Hub also accepts the `x-mcp-api-key` header:
```yaml
mcp_servers:
performance-hub:
url: "https://app.performancehub.co/mcp"
headers:
x-mcp-api-key: "${PH_MCP_API_KEY}"
timeout: 180
connect_timeout: 30
```
## Reload and test
Restart Hermes, or run this inside an active Hermes CLI session:
```text
/reload-mcp
```
Then confirm the connection:
```bash
hermes mcp list
hermes mcp test performance-hub
```
If the connection succeeds, the Performance Hub MCP tools become available to Hermes as normal tools.
## Troubleshooting
| Problem | Check |
|---|---|
| No tools appear | Restart Hermes or run `/reload-mcp`; verify `mcp_servers` is under the top-level config. |
| Unauthorized | Check the key value and the header format. |
| Key leaked | Deactivate/delete the key in Performance Hub and create a replacement. |
| Missing specific tools | Your Performance Hub user may not have the required facility, module, or admin permissions. |
## Sources
- Hermes CLI help (`hermes config edit`, `hermes mcp list`, `hermes mcp test`) and its MCP client implementation.
- Performance Hub MCP: https://app.performancehub.co/mcp
Source: https://docs.performancehub.co/ai-agents/clients/hermes
---
# Connect OpenClaw to Performance Hub
> Instructions current as of 17 June 2026 (AEST). Vendor MCP support changes frequently - verify against the linked sources if something looks different.
OpenClaw supports outbound MCP servers, including Streamable HTTP servers.
Performance Hub MCP server:
```text
https://app.performancehub.co/mcp
```
## OAuth setup
Use OAuth if your OpenClaw version and Performance Hub deployment complete the remote MCP OAuth flow successfully:
```bash
openclaw mcp add performancehub \
--url https://app.performancehub.co/mcp \
--transport streamable-http \
--auth oauth
openclaw mcp login performancehub
openclaw mcp doctor performancehub --probe
```
## Static key setup
If you are using an MCP API key, pass it as a header:
```bash
export PH_MCP_API_KEY='phk_your_key_here'
openclaw mcp add performancehub \
--url https://app.performancehub.co/mcp \
--transport streamable-http \
--header "Authorization: Bearer $PH_MCP_API_KEY" \
--timeout 20 \
--connect-timeout 5
openclaw mcp doctor performancehub --probe
```
Alternative header:
```bash
openclaw mcp add performancehub \
--url https://app.performancehub.co/mcp \
--transport streamable-http \
--header "x-mcp-api-key: $PH_MCP_API_KEY"
```
## Safety
Do not commit literal bearer tokens or MCP keys to OpenClaw config files. Use environment variables or OpenClaw's secret storage if available.
## Sources
- OpenClaw MCP docs: https://docs.openclaw.ai/cli/mcp
- Performance Hub MCP: https://app.performancehub.co/mcp
Source: https://docs.performancehub.co/ai-agents/clients/openclaw
---
# Connect Glean to Performance Hub
> Instructions current as of 17 June 2026 (AEST). Vendor MCP support changes frequently - verify against the linked sources if something looks different.
Glean, the enterprise search and AI agent platform, has documented support for connecting remote MCP servers.
Performance Hub MCP server:
```text
https://app.performancehub.co/mcp
```
## Glean setup
In Glean Admin, configure Performance Hub as a remote MCP server / action pack:
| Field | Value |
|---|---|
| MCP server URL | `https://app.performancehub.co/mcp` |
| Transport | Streaming HTTP / Streamable HTTP |
| Authentication | API key or OAuth, depending on your Glean configuration |
## Recommended auth: API key as bearer token
Glean's documented API-key mode sends the configured key as a bearer token in the `Authorization` header, which works with Performance Hub:
```http
Authorization: Bearer YOUR_PERFORMANCE_HUB_MCP_KEY
```
Do not use `x-mcp-api-key` in Glean unless Glean adds documented support for arbitrary API-key header names in your tenant.
## OAuth option
Glean documents OAuth modes for remote MCP servers, including admin/user OAuth and client credentials, depending on server support.
Performance Hub MCP OAuth metadata is available under:
```text
https://app.performancehub.co/mcp/.well-known/oauth-protected-resource
https://app.performancehub.co/mcp/.well-known/oauth-authorization-server
```
Use OAuth only after testing the flow in Glean.
## Tool selection
Publish only the tools the Glean agent needs. Large tool sets consume context and can reduce tool-selection accuracy.
Recommended starting set:
- `user_get_profile`
- `facility_get_config`
- The specific module tools required for the use case
## Agent instruction recommendation
```text
Use Performance Hub tools only when the user asks for Performance Hub data or actions. Confirm before state-changing actions. Use read-only tools first and include the facility context in the answer.
```
## Sources
- Connect remote MCP servers to Glean: https://docs.glean.com/administration/actions/connect-remote-mcp-servers-to-glean
- Supported MCP servers: https://docs.glean.com/administration/actions/supported-mcp-servers
- About Glean MCP: https://docs.glean.com/administration/platform/mcp/about
- External gateway tools: https://docs.glean.com/administration/platform/mcp/external-gateway-tools
- MCP security: https://docs.glean.com/administration/platform/mcp/security
- Performance Hub MCP: https://app.performancehub.co/mcp
Source: https://docs.performancehub.co/ai-agents/clients/glean
---
# Connect Microsoft Copilot Studio to Performance Hub
> Instructions current as of 17 June 2026 (AEST). Vendor MCP support changes frequently - verify against the linked sources if something looks different.
Microsoft Copilot Studio supports adding existing MCP servers to agents through the MCP onboarding wizard or through a Power Platform custom connector.
Performance Hub MCP server:
```text
https://app.performancehub.co/mcp
```
## Requirements
- A Copilot Studio agent with generative orchestration enabled.
- Access to add tools/actions to the agent.
- A Performance Hub MCP key, or a working OAuth setup.
Copilot Studio supports MCP over Streamable transport. Microsoft states that SSE transport is deprecated and is not supported for Copilot Studio MCP after August 2025.
## Recommended auth: API key header
Use the MCP onboarding wizard and choose API key authentication:
| Field | Value |
|---|---|
| Server URL | `https://app.performancehub.co/mcp` |
| Auth type | API key |
| API key location | Header |
| Header name | `x-mcp-api-key` |
| Header value | Your `phk_...` MCP key |
This matches Performance Hub's documented `x-mcp-api-key` support and avoids ambiguity around whether Copilot Studio prepends `Bearer` for API-key auth.
## OAuth option
Copilot Studio also documents OAuth 2.0 options, including dynamic discovery and manual OAuth configuration.
Use OAuth only after testing the Performance Hub OAuth flow in your Copilot Studio tenant. The MCP OAuth metadata is available under:
```text
https://app.performancehub.co/mcp/.well-known/oauth-protected-resource
https://app.performancehub.co/mcp/.well-known/oauth-authorization-server
```
## Add tools to the agent
Once the server is connected, Copilot Studio should discover the tools exposed to the authenticated Performance Hub account. Add the required tools/actions to the agent and test them in Copilot Studio.
## Governance
MCP access in Copilot Studio is governed through Power Platform connectors and data policies. Confirm that your tenant policies allow the Performance Hub MCP server before publishing the agent.
## Safety prompt recommendation
Add instructions to the Copilot Studio agent:
```text
Use Performance Hub MCP tools only for Performance Hub questions. Confirm with the user before changing device state, door/access settings, user access, display programs, orders, or running actions across multiple facilities. Use read/list tools before action tools.
```
## Sources
- Extend an agent with MCP: https://learn.microsoft.com/en-us/microsoft-copilot-studio/agent-extend-action-mcp
- Add an existing MCP server to an agent: https://learn.microsoft.com/en-us/microsoft-copilot-studio/mcp-add-existing-server-to-agent
- Performance Hub MCP: https://app.performancehub.co/mcp
Source: https://docs.performancehub.co/ai-agents/clients/copilot-studio
---
# Connect Salesforce Agentforce to Performance Hub
> Instructions current as of 17 June 2026 (AEST). Vendor MCP support changes frequently - verify against the linked sources if something looks different.
Salesforce has announced MCP support for Agentforce. Public documentation describes it as beta / account-qualified, so confirm availability and the exact auth fields in your own Salesforce org before publishing an agent.
Performance Hub MCP server:
```text
https://app.performancehub.co/mcp
```
## Current status
Public Salesforce sources describe Agentforce MCP support with:
- A centralized MCP server registry.
- Tool allowlisting.
- Security and governance controls.
- Authentication, granular access controls, policy enforcement, and rate limiting.
- MCP tools saved as actions in the Agentforce Asset Library.
- Adding selected MCP actions to topics in Agentforce Builder.
The accessible public sources did not specify the exact external MCP auth fields for arbitrary servers, such as static `Authorization: Bearer ...`, static `x-mcp-api-key`, OAuth Dynamic Client Registration, or manual OAuth client configuration. Do not assume any of these auth patterns are supported in Agentforce unless they are confirmed in your Salesforce org, beta documentation, or by Salesforce.
## Setup outline
If your Salesforce org has Agentforce MCP beta access:
1. In Salesforce Setup, register a new MCP server.
2. Use the server URL:
```text
https://app.performancehub.co/mcp
```
3. Configure authentication using the auth methods available in your Salesforce org.
4. Allowlist only the Performance Hub tools the agent needs.
5. Save the selected tools as actions in the Agentforce Asset Library.
6. In Agentforce Builder, open the agent/topic.
7. Add actions from the Asset Library.
8. Write clear instructions for each action.
9. Test in Plan Canvas before publishing.
## If MCP auth is not supported in your org
Use a Salesforce-supported integration path instead:
- A MuleSoft / API action that calls the Performance Hub convenience endpoints.
- An Apex action that calls a controlled backend service.
- A Flow action connected to a secure integration layer.
The MCP convenience endpoints are:
```text
GET https://app.performancehub.co/mcp/tools
POST https://app.performancehub.co/mcp/tools/:name
```
They still require a valid Performance Hub MCP credential.
## Safety prompt recommendation
```text
Use Performance Hub actions only when the user asks about Performance Hub. Do not run device, door, access, order, display, or multi-facility actions without explicit confirmation. Prefer read-only actions before state-changing actions.
```
## Sources
- Agentforce MCP support: https://www.salesforce.com/agentforce/mcp-support/
- Agentforce MCP blog: https://www.salesforce.com/blog/agentforce-mcp/
- How Agentforce works: https://www.salesforce.com/agentforce/how-it-works/
- Performance Hub MCP: https://app.performancehub.co/mcp
Source: https://docs.performancehub.co/ai-agents/clients/salesforce-agentforce
---
# Partner Types
Performance Hub partners connect the systems, devices, data pipelines, and delivery teams that help operators run physical locations more consistently.
Most technical integrations start with [Partner Console](https://docs.performancehub.co/api-docs/partner-console) access in a Performance Hub sandbox account. From there, partners generate test keys, configure facility linking, work through the [Partner API](https://docs.performancehub.co/api-docs/partner), validate their workflows, and then move through to production with the Performance Hub team.
## Member management systems
Member management and club management platforms usually keep the source of truth for people, plans, bookings, attendance, and access permissions. These integrations help Performance Hub understand who belongs to each facility, what they are entitled to access, and how those facilities are performing.
These partners often integrate with Performance Hub Door/Gate Controller workflows too. Performance Hub makes the door control hardware; the partner platform usually decides whether a person is allowed to enter, then calls the Partner API to trigger the Performance Hub controller.
Typical use cases:
- Sync members, leads, prospects, blocked users, and expired members.
- Sync membership plans, access zones, access hours, and pricing.
- Push check-ins and attendance events for reporting and model accuracy.
- Submit facility report snapshots for operational and financial visibility.
- Trigger Performance Hub Door/Gate Controllers after the partner system authorises access.
Recommended starting points:
- [Partner Console](https://docs.performancehub.co/api-docs/partner-console)
- [Authentication](https://docs.performancehub.co/api-docs/partner#authentication)
- [Members](https://docs.performancehub.co/api-docs/partner#members)
- [Membership Plans](https://docs.performancehub.co/api-docs/partner#membership-plans)
- [Check-Ins](https://docs.performancehub.co/api-docs/partner#check-ins)
- [Facility Reports](https://docs.performancehub.co/api-docs/partner#reports)
- [Access Control](https://docs.performancehub.co/api-docs/partner#access-control)
## Access and entry workflow partners
Access and entry workflow partners connect systems that influence whether someone should be admitted to a facility. This might include booking platforms, class attendance systems, visitor workflows, security providers, or member management systems with their own access rules.
Performance Hub does not expect partners to manufacture or supply the Door/Gate Controller hardware. Performance Hub provides and manages that hardware. Partner systems decide whether access should be granted, then call the Partner API to unlock a Performance Hub controller where the integration is enabled.
Typical use cases:
- List available Door/Gate Controllers for a facility.
- Check controller status before presenting an unlock option.
- Trigger a door or gate unlock after your system authorises access.
- Record a denial or attempt that never reached the controller, so it appears in Door & Gate Access Logs.
- Correlate unlocks with member and check-in IDs for audit trails.
- Connect bookings, appointments, classes, or visitor records to facility entry workflows.
Recommended starting points:
- [Partner Console](https://docs.performancehub.co/api-docs/partner-console)
- [Access Control](https://docs.performancehub.co/api-docs/partner#access-control)
- [List Door/Gate Controllers](https://docs.performancehub.co/api-docs/partner#access-control-doors-list)
- [Get Door/Gate Controller status](https://docs.performancehub.co/api-docs/partner#access-control-doors-get)
- [Unlock a Door/Gate Controller](https://docs.performancehub.co/api-docs/partner#access-control-doors-unlock)
- [Record an access attempt or denial](https://docs.performancehub.co/api-docs/partner#access-control-doors-access-events)
## BI, reporting, and analytics tools
Reporting and analytics partners help operators read and combine Performance Hub data with financial, membership, occupancy, operational, and external business datasets.
Today, most of this integration work is about reading organisations and facilities, aligning identifiers, and working with reporting data. As Performance Hub expands the Partner API, computer vision and facility intelligence data will become an important part of this partnership type.
Typical use cases:
- Read connected organisations and facilities.
- Read or exchange report snapshots for period-based reporting.
- Align facility identifiers across reporting tools and Performance Hub.
- Support dashboards for multi-site operators and leadership teams.
- Prepare for future access to computer vision, occupancy, and facility intelligence datasets.
Recommended starting points:
- [Partner Console](https://docs.performancehub.co/api-docs/partner-console)
- [Organisations](https://docs.performancehub.co/api-docs/partner#organisations)
- [Facilities](https://docs.performancehub.co/api-docs/partner#facilities)
- [Facility Reports](https://docs.performancehub.co/api-docs/partner#reports)
- [Partner Linking Methods](https://docs.performancehub.co/api-docs/partner-console#partner-linking-methods)
## Agencies and system integrators
Agencies and system integrators help deploy Performance Hub in the field. Their work is often more operational than API-only: installing and configuring IoT hardware, screens, devices, cameras, edge processors, access devices, and the supporting systems needed to bring a facility online.
Typical use cases:
- Deploy Performance Hub hardware across new or existing facilities.
- Install and configure cameras, screens, IoT devices, and edge hardware.
- Coordinate network, power, mounting, and commissioning requirements.
- Help connect facility systems, partner platforms, and operational workflows.
- Support testing, handover, and go-live readiness with Performance Hub.
Recommended starting points:
- [Become a Partner](https://docs.performancehub.co/api-docs/become-a-partner)
- [Partner Console](https://docs.performancehub.co/api-docs/partner-console)
- [Partner Linking Methods](https://docs.performancehub.co/api-docs/partner-console#partner-linking-methods)
- [Best Practices](https://docs.performancehub.co/api-docs/partner#best-practices)
- [Going-Live Checklist](https://docs.performancehub.co/api-docs/partner#going-live)
## Resellers and white-label distributors
Resellers and white-label distributors help bring Performance Hub into new markets, networks, and operating groups.
Typical use cases:
- Coordinate onboarding between Performance Hub, customers, and implementation partners.
- Package Performance Hub with local services, hardware, or operational support.
- Support customer rollout across multiple facilities or regions.
- Help customers discover compatible integrations in Performance Hub.
Recommended starting points:
- [Become a Partner](https://docs.performancehub.co/api-docs/become-a-partner)
- [Partner Console](https://docs.performancehub.co/api-docs/partner-console)
- [Support](https://docs.performancehub.co/api-docs/partner-resources/support)
- [Brand and Logo Guidelines](https://docs.performancehub.co/api-docs/partner-resources/branding)
## How partners appear inside Performance Hub
Performance Hub does not currently publish a public partner directory on the docs site. Approved partner integrations appear inside the Performance Hub partner directory in the core Performance Hub platform.
After sandbox testing, go-live checks, and production setup are complete, your configured integration will appear in the Performance Hub partner directory inside the core Performance Hub platform. This keeps the visible directory aligned with integrations that are actually configured and supported in the platform.
Source: https://docs.performancehub.co/api-docs/partner-types
---
# Partner FAQ
## Who should become a Performance Hub partner?
Partners typically include member management systems, access software and entry workflow vendors, reporting tools, agencies, system integrators, resellers, and white-label distributors.
See [Partner Types](https://docs.performancehub.co/api-docs/partner-types) for examples.
## How do I apply?
Submit the [Become a Partner](https://docs.performancehub.co/api-docs/become-a-partner) form with your company details, partner type, and intended use case. The Performance Hub team will review your enquiry and follow up with next steps.
## Do you have a public partner directory?
Not at this stage. After sandbox testing, go-live checks, and production setup are complete, your configured integration will appear in the Performance Hub partner directory inside the core Performance Hub platform.
## How do partners access API keys?
Partners manage secret keys in the [Partner Console](https://docs.performancehub.co/api-docs/partner-console). Keys can be live, dryrun, or read-only depending on the type of access needed.
## What is the difference between dryrun and live keys?
Dryrun keys are designed for testing write operations without affecting production data. Live keys can read and write production data according to the permissions configured for your partner account.
## How do facilities link to a partner integration?
Performance Hub supports two linking methods:
- **Direct Link** - partners use Performance Hub facility IDs.
- **Linking Identifier** - partners use their own branch, location, or store identifier.
See [Partner Linking Methods](https://docs.performancehub.co/api-docs/partner-console#partner-linking-methods).
## Can a partner integration unlock doors or gates?
Yes, where the integration is enabled for the facility and the Door/Gate Controller is available. Your system remains responsible for deciding whether a person is allowed to enter. Performance Hub sends the command and returns success only after the controller confirms the relay unlocked.
## What rate limits apply?
Partner API accounts start with default guidance:
- 5 requests per second per endpoint.
- 1000 items maximum per batch request for optimal performance.
- 10MB maximum payload size.
Rate limits are adjustable and can be configured on a case-by-case basis by your Performance Hub partner account manager. Discuss higher-throughput needs during onboarding, before migrations, or before large reporting backfills.
Design your integration to use bounded retries and exponential backoff.
## Can I use the Performance Hub logo on my website?
Yes, subject to the [Brand and Logo Guidelines](https://docs.performancehub.co/api-docs/partner-resources/branding). Send launch pages or co-marketing assets to the Performance Hub team for review before publishing.
## Where can I get support?
Use the [Support](https://docs.performancehub.co/api-docs/partner-resources/support) page for escalation guidance, bug report details, and status links.
Source: https://docs.performancehub.co/api-docs/partner-resources/faq
---
# Brand and Logo Guidelines
Use these guidelines when describing your integration with Performance Hub.
## Wordmark
The Performance Hub logo is a wordmark. Use the supplied SVG wherever possible so the mark stays crisp across light and dark surfaces.
## Clear space
Keep clear space around the wordmark so it is not crowded by other logos, text, buttons, or borders. As a rule of thumb, leave at least the height of the wordmark around all sides.
## Minimum size
Use the wordmark at a size where it remains readable. Avoid placing it below 120px wide in digital layouts.
## Colour and backgrounds
- Use the supplied SVG without recolouring it.
- Place the wordmark on backgrounds with enough contrast.
- Do not add shadows, outlines, gradients, or effects to the wordmark.
- Do not stretch, skew, crop, rotate, or rebuild the wordmark.
## Approved copy
Use clear, factual copy when referring to your integration:
- "Integrates with Performance Hub"
- "Available for Performance Hub customers"
- "Built for the Performance Hub Partner API"
Avoid implying endorsement, certification, or exclusivity unless Performance Hub has approved that language in writing.
## In-product partner directory
After sandbox testing, go-live checks, and production setup are complete, your configured integration will appear in the Performance Hub partner directory inside the core Performance Hub platform.
The docs site does not currently publish a public partner directory.
## Approval
Before using the Performance Hub wordmark in public launch materials, partner pages, paid campaigns, or co-marketing assets, send a preview to the Performance Hub team for review.
Include:
- Where the mark will appear.
- The final copy around the mark.
- Screenshots or links to the draft page or asset.
- Launch timing.
Source: https://docs.performancehub.co/api-docs/partner-resources/branding
---
# Developer and Partner Support
Use this page when you need help with Developer & Partner Resources, Partner Console access, Partner API behaviour, production launch readiness, or general Performance Hub support routes.
This support page is specific to the Developer & Partner Resources area. The main Performance Hub documentation support page remains available for general product documentation and non-partner workflows.
## Before contacting support
For faster triage on partner or API issues, collect:
- Partner name.
- Environment: staging, dryrun, or production.
- Endpoint and HTTP method.
- Timestamp and timezone.
- Facility identifier used.
- Source record ID from your system.
- HTTP status code and response body.
- Partner key ID if available. Do not send the secret key.
- `event_id` or `command_id` for access-control requests.
For general product or platform questions, include:
- Organisation or facility name.
- The page, feature, or workflow you are using.
- Screenshots or screen recordings where helpful.
- Browser and device details if the issue is UI-related.
- Any error message shown in Performance Hub.
## Support channels
### Partner enquiries
Prospective partners should use the [Become a Partner](https://docs.performancehub.co/api-docs/become-a-partner) form. This routes your enquiry to the Performance Hub partner team.
### Existing Performance Hub customers
Performance Hub customers can submit support tickets from inside the main Performance Hub software.
Use this path for general product questions, facility configuration, device issues, account access, and operational support.
### Developer and API support
Performance Hub partners can submit escalated support tickets through the Partner Portal.
Use the Partner Portal for Partner API, Partner Console, OpenAPI specifications, sample projects, integration issues, production launch readiness, and customer-impacting partner incidents.
Include the partner/API triage details listed above.
For production incidents, include the details above and the business impact, such as affected facilities, blocked workflows, or failed access events.
## Platform status
Check the Performance Hub status page for active incidents and uptime information:
[View platform status](https://status.performancehub.co/status/performancehub)
## Escalation guidance
Escalate immediately when:
- A production access-control flow is unavailable.
- Multiple facilities cannot sync member, plan, check-in, or report data.
- A production facility cannot use a configured partner integration.
- A Performance Hub customer cannot access a core operational workflow.
- A secret key is suspected to be exposed.
- Customers are blocked from enabling or using your integration.
For lower-priority issues, include reproduction steps and whether the issue occurs in staging, dryrun, or production.
## Change windows
If your integration will run a large migration, backfill, or production launch, coordinate timing with the Performance Hub team. Share:
- Planned start and end time.
- Facilities affected.
- Expected request volume.
- Rollback plan.
- Technical contact during the change window.
Source: https://docs.performancehub.co/api-docs/partner-resources/support
---
# Partner Console
The Partner Console provides a web interface for managing your integration with Performance Hub.
> If you do not yet have access to the Partner Console, please reach out to us by submitting a partner enquiry via the [Performance Hub Main Website](https://performancehub.co/contact/). Our team will guide you through the partner onboarding process and help set up your console access.
## Accessing the Console
To access the Partner Console, you'll need:
1. **Partner ID** - A unique identifier provided by Performance Hub to each partner
2. **One-Time Password (OTP)** - A verification code sent to your Partner Email
Log in at [partner-api.performancehub.co/partner/v1/console](https://partner-api.performancehub.co/partner/v1/console)

## What's in the Console
The Partner Console provides tools to manage your integration with Performance Hub:
- **Secret Keys** - Generate and manage API keys with different access levels (live, dryrun, read-only)
- **Integration Settings** - Configure how your integration links with facilities
- **Partner Details** - View and update your partner profile information
- **Logs** - Monitor API activity and troubleshoot issues
- **Connected Organisations** - See all organisations using your integration
- **Ask AI** - Get instant help with ⌘I shortcut
For detailed information about each feature, see [Partner Console Features](https://docs.performancehub.co/api-docs/partner-console#partner-console-features).
Source: https://docs.performancehub.co/api-docs/partner-console/partner-console
---
# Partner Console Features
The Partner Console includes four main sections accessible via the navigation tabs:
## 1. Secret Keys
Manage your API secret keys with full control over access types and permissions.
**Key Features:**
- **Create New Keys** - Generate new API keys with specific access levels
- **Key Types:**
- **Live** - Full read-write access to production data
- **Dryrun** - Test write operations without affecting production data (writes are simulated)
- **Read-only** - Read-only access for safe data consumption (write operations blocked)
- **Key Management** - View key details, last used timestamps, rotate keys, and revoke keys instantly
- **Key Filtering** - Filter keys by status (All, Active, Revoked)
- **Key Rotation** - Seamlessly rotate keys without downtime
Each secret key displays:
- **Label** - Customisable name for easy identification
- **Key ID** - Unique identifier (UUID format)
- **Secret** - Partially redacted secret key (first 12 and last 4 characters visible)
- **Type** - Key access level (live, dryrun, read-only)
- **Status** - Current state (active, revoked)
- **Created** - Date when the key was generated
- **Last Used** - Timestamp of most recent API call
- **Actions** - Rotate or revoke key
## 2. Integration Settings
Configure how your integration links with Performance Hub facilities.
**Key Features:**
- **Data Linking Method** - Choose between two linking methods:
- **Direct Link** - Use Performance Hub facility IDs for API calls
- **Linking Identifier** - Use your own location identifiers (Branch ID, Store Number, etc.)
- [Learn more about Partner Linking Methods](https://docs.performancehub.co/api-docs/partner-console#partner-linking-methods)
- **Linking Identifier Label** - Customise the field name displayed to facilities (e.g., "Branch ID", "Location Code")
- **Partner Prefix** - Set a unique prefix for your integrations
- **Integration Fields** - Configure custom fields specific to your integration
## 3. Partner Details
View and manage your partner profile information.
**Editable Fields:**
- **Partner Email** - Primary contact email for notifications
- **Description** - Brief description of your integration
- **Logo URL** - URL to your partner logo
- **Website** - Your company or integration website
- **Category** - Integration category classification
- **Details** - Additional partner information
**Read-Only Information:**
- Partner ID (unique identifier)
- Partner Name
- Integration Status
- Connected Organisations Count
## 4. Logs
Monitor API activity and troubleshoot integration issues.
**Available Data:**
- API Request Logs
- Response Status Codes
- Error Messages
- Timestamp Information
- Request/Response Payloads
## Connected Organisations
View all organisations that have enabled your integration. Each organisation card displays:
- Organisation Name
- Associated Brands
- Number of Active Facilities
- Integration Status
## Ask AI
Get instant help with partner-related topics using the AI assistant (⌘I shortcut).
**Ask About:**
- API endpoint usage and examples
- Authentication and secret key management
- Data synchronisation best practices
- Troubleshooting common integration issues
- Partner Console features and navigation
Source: https://docs.performancehub.co/api-docs/partner-console/partner-console-features
---
# Partner Linking Methods
Different partners or member management systems identify each facility or location by their own ID, such as a branch ID, store number or facility ID. We have two types of ways to identify locations during the syncing or pushing of data.
## 1. Direct Link
When pushing data, the partner will use the facility ID from the Performance Hub system.
For example, for Partner A, their internal ID for a facility in Tokyo is `branch_tokyo`. But to normalise the data within the Performance Hub ecosystem, we assign a new ID for this location and this partner, for example UUID: `550e8400-e29b-41d4-a716-446655440000`.
Partners should use this `facility_id` when calling our APIs to push data.
To obtain the correct `facility_id` for linking (as required in the Direct Link method), partners can call the `/partner/v1/facilities` API endpoint. This endpoint will return a list of all facilities that currently have your partner integration enabled in Performance Hub, along with their corresponding `facility_id` values.
### Example: Direct Link
```bash
curl -X POST https://partner-api.performancehub.co/partner/v1/membership-plans \
-u sk_live_YOUR_PARTNER_SECRET_KEY: \
-H "Content-Type: application/json" \
-d '{
"membership_plans": [
{
"membership_plan_id": "plan_unlimited_monthly",
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Unlimited Monthly Access",
"billing_cycle": "monthly",
"price": 10058,
"currency": "JPY"
}
]
}'
```
----
## 2. Linking Identifier
The partner maintains their own system with unique identifiers for each location (e.g., "Branch ID", "Location Code", "Store Number"). This linking method allows the partner to identify Performance Hub facilities using their own identifier rather than our Facility ID.
**Facility Setup:** When a facility enables this partner, they will be prompted to provide the unique identifier that the partner uses to identify their location in their facility configuration within the Performance Hub.
**API Integration:** When the partner makes API calls to Performance Hub, they can provide their own unique identifier in the request. Performance Hub will internally look up the `linking_identifier` to match the API call to the correct facility.
**Example:** If the partner calls locations "Branch ID" in their system, set the label below to "Branch ID". Each facility will then provide their Branch ID value when enabling the integration, allowing seamless data synchronisation between systems.

***Example configuration from a Facility Integrations Screen (Main Performance Facility View) showing the linking identifier / internal ID from the member management system configured in the partner integrations screen***
### Example: Linking Identifier
```bash
curl -X POST https://partner-api.performancehub.co/partner/v1/membership-plans \
-u sk_live_YOUR_PARTNER_SECRET_KEY: \
-H "Content-Type: application/json" \
-d '{
"membership_plans": [
{
"membership_plan_id": "plan_unlimited_monthly",
"linking_identifier": "branch_tokyo",
"name": "Unlimited Monthly Access",
"billing_cycle": "monthly",
"price": 10058,
"currency": "JPY"
}
]
}'
```
Source: https://docs.performancehub.co/api-docs/partner-console/partner-linking-methods
---
# Access Control
The Partner API can trigger Performance Hub Door/Gate Controller hardware for facilities where your integration is enabled.
Use this API when your member management system has already decided that a person is allowed to enter. Performance Hub does not evaluate membership status, access hours, door-zone rules, fraud checks, or suspension logic. Your system remains the authority for those decisions.
The unlock request is synchronous. Performance Hub sends the command to the on-site controller in real time and returns `200` only after the device confirms that the relay physically unlocked. Most confirmations should complete in 3-5 seconds. If no confirmation is received within the timeout window, the API returns an explicit failure.
You can also record an access attempt or denial that never reached the controller. That call does not unlock the door. It writes an event to Door & Gate Access Logs.
### Endpoints
```
GET /partner/v1/access-control/doors - List available Door/Gate Controllers
GET /partner/v1/access-control/doors/{door_id} - Get Door/Gate Controller status
POST /partner/v1/access-control/doors/{door_id}/unlock - Unlock a Door/Gate Controller
POST /partner/v1/access-control/doors/{door_id}/access-events - Record an access attempt or denial
```
Source: https://docs.performancehub.co/api-docs/partner/access-control
---
# Record an access attempt or denial
Records an access attempt or denial that your system has already decided. The Door/Gate Controller is not contacted, and the door does not unlock.
Use this when your system refuses entry, or when an attempt should appear in Door & Gate Access Logs without an unlock. Performance Hub does not evaluate membership status, access hours, or other access rules.
A denied event appears in Door & Gate Access Logs with a Denied outcome, your partner name, the door, the member identifier when you send one, and the reason code in a readable form. For example, `membership_expired` is shown as "Membership Expired". `reason_display_text` and `user_email` are stored with the event.
## Parameters
- **door_id** `string` required
Door/Gate Controller ID returned by the list endpoint.
- **facility_id** `string` either/orProvide the facility identifier type configured for your integration: use facility_id for direct-link integrations, or linking_identifier for linking-identifier integrations. Do not send both; the API uses the identifier type configured for your partner account and ignores the other one. Learn more
Performance Hub facility ID. Required for direct-link integrations.
- **linking_identifier** `string` either/orProvide the facility identifier type configured for your integration: use facility_id for direct-link integrations, or linking_identifier for linking-identifier integrations. Do not send both; the API uses the identifier type configured for your partner account and ignores the other one. Learn more
Partner facility identifier. Required for linking-identifier integrations.
- **outcome** `string` required
`denied` or `attempted`.
- **reason** `string` optional
Lowercase snake_case reason code, up to 64 characters. Required when `outcome` is `denied`. Example: `membership_expired`.
- **reason_display_text** `string` optional
Human-readable reason, up to 256 characters. Example: `Membership expired on 2026-04-01`.
- **member_id** `string` optional
Partner member ID. Omit it when the credential did not match a member, such as an unknown card.
- **user_email** `string` optional
Email of the person who attempted access.
- **timestamp** `string` optional
When the attempt happened, as ISO 8601 with a timezone. Example: `2026-05-18T10:15:30Z`. Defaults to the time the request is received. Must not be more than 5 minutes in the future or older than 30 days.
- **idempotency_key** `string` optional
Replay-protection key, up to 128 characters. Reusing the same key for the same partner, facility, and door returns the original result.
## Returns
Returns `201` when the event is recorded. Replaying the same `idempotency_key` returns `200` with `idempotent` set to `true` and does not create another event.
A dry-run key returns `200` with `dry_run` set to `true`. Nothing is recorded.
Common failures:
- `400` - the body is invalid, a denial has no `reason`, the timestamp is out of range, or `facility_id` / `linking_identifier` does not match a linked facility.
- `403` - the key is read-only.
- `403 integration_not_enabled` - your integration is not enabled for the facility. `message` gives the reason.
- `404` - the Door/Gate Controller was not found for the linked facility.
### Request
```bash
curl -X POST https://partner-api.performancehub.co/partner/v1/access-control/doors/00000000000000000000000000000007/access-events \
-u sk_live_YOUR_PARTNER_SECRET_KEY: \
-H "Content-Type: application/json" \
-d '{
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
"outcome": "denied",
"reason": "membership_expired",
"reason_display_text": "Membership expired on 2026-04-01",
"member_id": "mem_12345",
"user_email": "member@example.com",
"timestamp": "2026-05-18T10:15:30Z",
"idempotency_key": "evt_abc12345"
}'
```
### Response
```json
{
"status": "recorded",
"event_id": "00000000-0000-0000-0000-000000000045",
"outcome": "denied",
"door_id": "00000000000000000000000000000007",
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-05-18T10:15:30.000Z"
}
```
### Idempotent replay
```json
{
"status": "recorded",
"event_id": "00000000-0000-0000-0000-000000000045",
"outcome": "denied",
"door_id": "00000000000000000000000000000007",
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-05-18T10:15:30.000Z",
"idempotent": true
}
```
Source: https://docs.performancehub.co/api-docs/partner/access-control/doors/access-events
---
# Get Door/Gate Controller status
Retrieves the latest known state and basic configuration for a single Door/Gate Controller.
Provide a facility identifier so Performance Hub can verify that the door belongs to a facility your integration can access.
## Parameters
- **door_id** `string` required
Door/Gate Controller ID returned by the list endpoint.
- **facility_id** `string` either/orProvide the facility identifier type configured for your integration: use facility_id for direct-link integrations, or linking_identifier for linking-identifier integrations. Do not send both; the API uses the identifier type configured for your partner account and ignores the other one. Learn more
Performance Hub facility ID. Required for direct-link integrations.
- **linking_identifier** `string` either/orProvide the facility identifier type configured for your integration: use facility_id for direct-link integrations, or linking_identifier for linking-identifier integrations. Do not send both; the API uses the identifier type configured for your partner account and ignores the other one. Learn more
Partner facility identifier. Required for linking-identifier integrations.
## Returns
Returns a Door/Gate Controller object.
### Request
```bash
curl -X GET "https://partner-api.performancehub.co/partner/v1/access-control/doors/00000000000000000000000000000007?facility_id=550e8400-e29b-41d4-a716-446655440000" \
-u sk_live_YOUR_PARTNER_SECRET_KEY:
```
### Response
```json
{
"data": {
"door_id": "00000000000000000000000000000007",
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Front Door",
"zone": "Entrance",
"status": "online",
"relay_state": "locked",
"maintenance_mode": "off",
"hold_time_seconds": 10,
"last_seen": "2026-05-01T04:30:00.000Z"
}
}
```
Source: https://docs.performancehub.co/api-docs/partner/access-control/doors/get
---
# List Door/Gate Controllers
Returns Door/Gate Controllers available to your integration.
If neither `facility_id` nor `linking_identifier` is provided, the API returns Door/Gate Controllers across all facilities linked to your partner account.
Recommended: Include a facility identifier whenever possible. Requests without `facility_id` or `linking_identifier` may take longer to load for partners with hundreds or thousands of Door/Gate Controllers.
## Parameters
- **facility_id** `string` optional either/orProvide the facility identifier type configured for your integration: use facility_id for direct-link integrations, or linking_identifier for linking-identifier integrations. Do not send both; the API uses the identifier type configured for your partner account and ignores the other one. Learn more
Performance Hub facility ID. Use this for direct-link integrations.
- **linking_identifier** `string` optional either/orProvide the facility identifier type configured for your integration: use facility_id for direct-link integrations, or linking_identifier for linking-identifier integrations. Do not send both; the API uses the identifier type configured for your partner account and ignores the other one. Learn more
Your own facility/location identifier. Use this when your integration is configured for linking identifiers.
## Returns
Returns an array of Door/Gate Controller objects.
### Request
```bash
curl -X GET "https://partner-api.performancehub.co/partner/v1/access-control/doors?facility_id=550e8400-e29b-41d4-a716-446655440000" \
-u sk_live_YOUR_PARTNER_SECRET_KEY:
```
### Response
```json
{
"data": [
{
"door_id": "00000000000000000000000000000007",
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Front Door",
"zone": "Entrance",
"status": "online",
"relay_state": "locked",
"maintenance_mode": "off",
"hold_time_seconds": 10,
"last_seen": "2026-05-01T04:30:00.000Z"
}
]
}
```
Source: https://docs.performancehub.co/api-docs/partner/access-control/doors/list
---
# The Door/Gate Controller object
## Attributes
- **door_id** `string` required
Unique identifier for the Door/Gate Controller. Store this in your system after discovery and use it when calling the unlock endpoint.
- **facility_id** `string` required
Performance Hub facility ID.
- **linking_identifier** `string` optional
Partner facility identifier when your integration uses linking identifiers. [Learn more](https://docs.performancehub.co/api-docs/partner-console#partner-linking-methods)
- **name** `string` required
Human-readable device name configured in Performance Hub.
- **zone** `string` optional
Door zone or location, such as `Entrance`.
- **status** `string` required
`online` or `offline`.
- **relay_state** `string` optional
Latest known relay state, such as `locked` or `unlocked`.
- **maintenance_mode** `string` required
Current maintenance mode. One of `off`, `force-open`, or `force-closed`.
- **hold_time_seconds** `integer` required
Default unlock hold time configured for the door.
- **last_seen** `string` optional
ISO timestamp for the latest device metadata update.
### The Door/Gate Controller object
```json
{
"door_id": "00000000000000000000000000000007",
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
"linking_identifier": "branch_downtown",
"name": "Front Door",
"zone": "Entrance",
"status": "online",
"online": true,
"relay_state": "locked",
"maintenance_mode": "off",
"hold_time_seconds": 10,
"last_seen": "2026-05-01T04:30:00.000Z"
}
```
Source: https://docs.performancehub.co/api-docs/partner/access-control/doors/object
---
# Unlock a Door/Gate Controller
Sends an unlock command to the Door/Gate Controller and waits for the physical relay to confirm it has unlocked.
Your system must decide whether the person is authorised before calling this endpoint. Performance Hub only verifies the partner key, facility access, door ownership, and device state.
## Parameters
- **door_id** `string` required
Door/Gate Controller ID returned by the list endpoint.
- **facility_id** `string` either/orProvide the facility identifier type configured for your integration: use facility_id for direct-link integrations, or linking_identifier for linking-identifier integrations. Do not send both; the API uses the identifier type configured for your partner account and ignores the other one. Learn more
Performance Hub facility ID. Required for direct-link integrations.
- **linking_identifier** `string` either/orProvide the facility identifier type configured for your integration: use facility_id for direct-link integrations, or linking_identifier for linking-identifier integrations. Do not send both; the API uses the identifier type configured for your partner account and ignores the other one. Learn more
Partner facility identifier. Required for linking-identifier integrations.
- **member_id** `string` required
Partner member ID. This is used for audit logging and later data linking.
- **first_name** `string` required
First name of the person opening the door.
- **last_name** `string` required
Last name of the person opening the door.
- **full_name** `string` optional
Optional. If omitted, Performance Hub combines `first_name` and `last_name`.
- **check_in_id** `string` optional
Optional check-in ID from your system for correlation.
- **idempotency_key** `string` optional
Optional replay-protection key. Reusing the same key for the same partner, facility, and door returns the existing result when available.
## Returns
Returns `200` only when the device confirms the relay physically unlocked. The door uses the hold time configured in Performance Hub Device Management; partners cannot override relay timing per request. Most confirmations should complete in 3-5 seconds. Treat the documented timeout as an explicit failed unlock and retry only according to your own safety rules.
Common failures:
- `403 integration_not_enabled` - your integration is not enabled for the facility.
- `409 maintenance_mode_active` - facility staff have enabled maintenance mode for the door.
- `503 device_offline` - the Door/Gate Controller is offline or stale.
- `504 unlock_confirmation_timeout` - the command was sent but no matching confirmation was received before timeout.
### Request
```bash
curl -X POST https://partner-api.performancehub.co/partner/v1/access-control/doors/00000000000000000000000000000007/unlock \
-u sk_live_YOUR_PARTNER_SECRET_KEY: \
-H "Content-Type: application/json" \
-d '{
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
"member_id": "mem_12345",
"first_name": "Jane",
"last_name": "S.",
"full_name": "Jane S.",
"source": "door_reader",
"idempotency_key": "unlock_20260501_0001"
}'
```
### Response
```json
{
"status": "unlocked",
"door_id": "00000000000000000000000000000007",
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
"event_id": "00000000-0000-0000-0000-000000000045",
"command_id": "00000000-0000-0000-0000-000000000049",
"hold_time_seconds": 10,
"confirmed_at": "2026-05-01T04:30:00.000Z"
}
```
### Failure response
```json
{
"status": "failed",
"reason": "device_offline",
"door_id": "00000000000000000000000000000007",
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
"event_id": "00000000-0000-0000-0000-000000000045"
}
```
Source: https://docs.performancehub.co/api-docs/partner/access-control/doors/unlock
---
# Authentication
Access to the Partner API requires authentication using API secret keys. Generate and manage your keys through the [Partner Console](https://docs.performancehub.co/api-docs/partner-console).
API keys serve as your credentials for accessing partner resources. Each key has restricted access to specific organisation data and can be deactivated instantly when needed.
**Handle your API keys with care—they provide full access to your account!** Never expose these keys in public repositories, client-side applications, or anywhere they could be compromised.
Authenticate requests using HTTP Basic Auth by passing your API key as the username. Leave the password empty.
**The API only accepts secure HTTPS connections.** Plain HTTP requests are rejected, as are any requests missing proper authentication credentials.
### Example Request
```bash
curl https://partner-api.performancehub.co/partner/v1/organisations \
-u sk_live_YOUR_PARTNER_SECRET_KEY:
# The colon prevents curl from asking for a password.
```
### Verify Secret Key
```bash
curl https://partner-api.performancehub.co/partner/v1/auth/ping \
-u sk_live_YOUR_PARTNER_SECRET_KEY:
# Returns { "message": "You are successfully authorised as " }
```
Source: https://docs.performancehub.co/api-docs/partner/authentication
---
# Best Practices
Use these patterns to keep partner integrations predictable, observable, and safe for facilities.
## Identify facilities consistently
Choose the linking method configured for your partner account and use it consistently:
- **Direct Link** - use the Performance Hub `facility_id` returned by the facilities endpoint.
- **Linking Identifier** - use your own location identifier, such as a branch ID, location code, or store number.
Do not send both identifiers in the same request. Performance Hub resolves requests using the linking method configured for your partner account.
See [Partner Linking Methods](https://docs.performancehub.co/api-docs/partner-console#partner-linking-methods) for setup guidance.
## Batch writes carefully
The Partner API accepts JSON request bodies and is designed for batch-oriented sync workflows where documented. Keep batches bounded and retryable.
Default guidance:
- Use up to **1000 items per batch request** for optimal performance.
- Keep payloads under **10MB**.
- Stay within **5 requests per second per endpoint**.
Rate limits are adjustable and can be configured on a case-by-case basis by your Performance Hub partner account manager. If you expect high-volume imports, launch traffic, or reporting backfills, agree the required throughput before the change window.
If you need to sync a large dataset, split it into deterministic chunks and log each chunk ID on your side. This makes failed retries easier to reason about.
## Retry safely
Use exponential backoff for transient failures such as `429`, `500`, `502`, `503`, and `504`.
Avoid tight retry loops. A good starting point is:
1. Retry after 1 second.
2. Retry after 2 seconds.
3. Retry after 4 seconds.
4. Stop and alert if the operation still fails.
For validation failures such as `400` or `422`, fix the request before retrying.
## Use idempotency where supported
The Door/Gate Controller unlock endpoint supports an optional `idempotency_key`. Reusing the same key for the same partner, facility, and door returns the existing result when one is available.
Use a stable key for the action being performed, for example a check-in ID or access event ID from your system.
Do not assume every write endpoint supports idempotency unless it is documented for that endpoint.
## Keep source data clean
Send stable, normalized values wherever possible:
- Use consistent member, plan, facility, and check-in IDs from your source system.
- Use ISO 8601 timestamps for dates and times.
- Send phone numbers in international format where available.
- Send currencies using the fields documented for each endpoint.
- Avoid overloading free-text fields with data that belongs in structured fields.
## Design for incremental and full syncs
Most partner integrations need both:
- **Incremental syncs** for day-to-day changes.
- **Full reconciliation syncs** to correct missed events, historical imports, or data drift.
Your integration should be able to resend the current state for a member, membership plan, or report snapshot without depending on a previous request having succeeded.
## Publish Access Time Windows safely
Treat each member's `access_time_windows` value as a complete, independently revisioned snapshot:
- Coordinate one monotonically increasing `access_time_windows_revision` stream across incremental and reconciliation workers.
- Retry an uncertain or transient delivery with the same revision and normalized payload.
- Omit `access_time_windows` when you are not updating it.
- Send `[]` only after a successful authoritative source fetch proves that no windows remain. Never turn an upstream timeout, partial result, or parse failure into revocation.
- Keep `external_id` and `scope_key` stable, and certify every scope mapping before production.
- Monitor source age against the freshness policy agreed for that integration.
See the authoritative [Access Time Windows producer guide](#members-access-time-windows) for complete snapshots, timezones, DST, cancellation, and test vectors.
## Treat access control as safety-critical
For Door/Gate Controller unlocks:
- Your system must decide whether the person is allowed to enter before calling Performance Hub.
- Performance Hub returns `200` only after the on-site controller confirms the relay physically unlocked.
- Most confirmations complete in 3-5 seconds.
- A timeout is an explicit failed unlock, not a background success.
- Retry only according to your own access and safety rules.
Always log the `event_id` and `command_id` returned by the unlock endpoint.
## Monitor your integration
Use the Partner Console request logs during development and launch. In production, also maintain your own logs with:
- Request timestamp.
- Endpoint.
- Source record ID.
- Facility identifier.
- HTTP status code.
- Performance Hub response body or error message.
These fields make support and reconciliation much faster when a customer asks what happened.
Source: https://docs.performancehub.co/api-docs/partner/best-practices
---
# Changelog
This changelog records notable Partner API and Partner Console changes that affect integration behaviour.
## 2026-09-09
### Added
- Added vendor-neutral Member `access_time_windows` snapshots with mixed absolute and recurring weekly windows.
- Added `access_time_windows_revision` ordering, idempotent replay, conflict, stale-skip, and explicit-revocation semantics.
- Added IANA timezone, daylight-saving, logical `scope_key` mapping, source-freshness, producer implementation, and certification guidance.
## 2026-05-06
### Added
- Added partner program documentation covering partner types, onboarding, best practices, go-live readiness, errors, versioning, and support.
- Added a dedicated Become a Partner enquiry flow for prospective technical and commercial partners.
- Added documentation for how approved partner integrations appear inside the Performance Hub partner directory in the core Performance Hub platform.
## 2026-05-01
### Added
- Added Door/Gate Controller access-control documentation, including list, status, and unlock operations.
- Added synchronous unlock behaviour documentation: the API returns success only after the on-site controller confirms the relay physically unlocked.
- Added optional `idempotency_key` support documentation for Door/Gate Controller unlock requests.
## Change categories
Future entries use these categories:
- **Added** - new endpoint, field, guide, or capability.
- **Changed** - behaviour or documentation changed without removing support.
- **Deprecated** - feature remains available but has a planned migration path.
- **Removed** - feature or endpoint removed.
- **Fixed** - bug fix or documentation correction.
- **Security** - authentication, authorization, data handling, or key-management change.
Source: https://docs.performancehub.co/api-docs/partner/changelog
---
# Check-Ins
Represents a record of a person being present at a facility, class, or activity. Used for tracking attendance, usage analytics, and improving visual detection accuracy.
### Endpoints
```
POST /partner/v1/check-ins - Save check-ins
```
Source: https://docs.performancehub.co/api-docs/partner/check-ins
---
# The Check-In object
## Attributes
- **check_in_id** `string` optional
Unique identifier for the check-in. Optional - auto-generated from `member_id` and `timestamp` pair if not provided.
- **member_id** `string` required
Unique identifier of the member.
- **facility_id** `string` either/orProvide the facility identifier type configured for your integration: use facility_id for direct-link integrations, or linking_identifier for linking-identifier integrations. Do not send both; the API uses the identifier type configured for your partner account and ignores the other one. Learn more
Unique identifier of the facility from Performance Hub.
- **linking_identifier** `string` either/orProvide the facility identifier type configured for your integration: use facility_id for direct-link integrations, or linking_identifier for linking-identifier integrations. Do not send both; the API uses the identifier type configured for your partner account and ignores the other one. Learn more
Unique identifier value for each location that the partner uses (e.g., "Branch ID", "Location Code", "Store Number"). [Learn more](https://docs.performancehub.co/api-docs/partner-console#partner-linking-methods)
- **check_in_type** `string` optional
Type of visit. One of `member`, `class`, `staff`, `guest`, or `trial`.
- **source** `string` optional
How the check-in was captured. Common values include `manual`, `tag`, `rfid`, `kiosk`, `app`. Additional values like `door_reader` or `booking` may also be used.
- **class_id** `string` optional
Optional. If tied to a class booking or attendance.
- **timestamp** `string` required
ISO 8601 format timestamp of when the check-in occurred.
### The Check-In object
```json
{
// "check_in_id": "checkin_abc123", // Optional - auto-generated from member_id + timestamp if not provided
"member_id": "mem_12345",
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
// "linking_identifier": "branch_downtown", // If using linking identifier
"check_in_type": "member",
"source": "rfid",
"timestamp": "2025-01-20T08:45:30Z"
}
```
Source: https://docs.performancehub.co/api-docs/partner/check-ins/object
---
# Save check-ins
Creates check-in records. The API automatically deduplicates based on member, facility, and timestamp.
## Parameters
- **check_ins** `array` required
Array of Check-In objects to save. Recommended maximum: 1000 items per request for optimal performance. The API automatically batches items for processing. Each item must include either `facility_id` (required if not using `linking_identifier`) or `linking_identifier` (required if not using `facility_id`).
## Returns
Returns a simple message confirming the sync operation.
### Request
```bash
curl -X POST https://partner-api.performancehub.co/partner/v1/check-ins \
-u sk_live_YOUR_PARTNER_SECRET_KEY: \
-H "Content-Type: application/json" \
-d '{
"check_ins": [
{
// "check_in_id": "checkin_abc123", // Optional - auto-generated from member_id + timestamp if not provided
"member_id": "mem_12345",
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
// "linking_identifier": "branch_downtown", // If using linking identifier
"check_in_type": "member",
"source": "rfid",
"timestamp": "2025-01-20T08:45:30Z"
}
]
}'
```
### Response
Returns a success message with metadata about the sync operation.
```json
{
"message": "Check-in sync successful.",
"metadata": {
"success": 523,
"skipped": 3,
"skipped_items": [
{
"member_id": "member_789",
"facility_id": null,
"reason": "No matching facility_id for the linking_identifier"
}
]
}
}
```
**Metadata fields:**
- **success** - Number of check-ins successfully processed
- **skipped** - Number of check-ins skipped (if any)
- **skipped_items** - Array of skipped check-ins with reasons (if any)
- **dry-run** - Boolean flag if using dryrun key type (if applicable)
Source: https://docs.performancehub.co/api-docs/partner/check-ins/save
---
# Errors
The Partner API uses standard HTTP status codes and JSON response bodies to explain why a request failed.
## Error response shape
Most API errors return a JSON body with a message and, where available, structured details.
```json
{
"success": false,
"message": "Request body.members[0].member_id is required"
}
```
Some domain-specific endpoints return an explicit status and reason. For example, access-control failures include fields such as `reason`, `door_id`, `facility_id`, and `event_id`.
```json
{
"status": "failed",
"reason": "device_offline",
"door_id": "00000000000000000000000000000007",
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
"event_id": "00000000-0000-0000-0000-000000000045"
}
```
## Status codes
### 400 Bad Request
The request is malformed or missing required fields. Fix the request before retrying.
### 401 Unauthorized
Authentication failed. Check that you are using HTTP Basic Auth with your API secret key as the username and an empty password.
### 403 Forbidden
The key is valid but the operation is not allowed. Common causes include using a read-only key for a write operation or attempting to access a facility where your integration is not enabled.
### 404 Not Found
The requested resource was not found or is not available to your integration.
### 409 Conflict
The request conflicts with the current resource state. For access control, this can include a Door/Gate Controller being in maintenance mode.
### 422 Unprocessable Entity
The request is valid JSON but fails semantic validation. Fix the data before retrying.
### 429 Too Many Requests
The integration exceeded the documented rate limit. Retry with exponential backoff.
### 500 Internal Server Error
Performance Hub could not complete the request because of an internal error. Retry with backoff. If the problem continues, contact support with the endpoint, timestamp, and response body.
### 503 Service Unavailable
A dependent service or device is unavailable. For Door/Gate Controller unlocks, this can mean the controller is offline.
### 504 Gateway Timeout
The operation timed out. For Door/Gate Controller unlocks, this means the unlock was not confirmed before the timeout window.
## Access Time Window failures
Window validation fails before the invalid snapshot is written. Common non-retryable causes include:
- Missing `access_time_windows_revision` when `access_time_windows` is present.
- String, floating-point, or likely-millisecond epoch values.
- An invalid IANA timezone, inverted interval, duplicate `external_id`, overlapping weekly blocks, or an overnight block that was not split.
- A value outside documented count, length, or span limits.
A repeated revision with different normalized content is a `409 Conflict`. Reconcile against the latest authoritative state and allocate a newer revision; do not keep retrying the conflicting pair. An older revision is reported as a stale skipped snapshot rather than allowed to overwrite newer state. An unknown or unmapped `scope_key` fails closed for authorization and should be treated as a configuration alert.
If delivery times out or receives a transient `5xx`, retry the exact normalized snapshot with the same revision. If the upstream source fetch failed, omit the window field entirely; never substitute an empty array.
## Retry guidance
Retry these with bounded exponential backoff:
- `429`
- `500`
- `502`
- `503`
- `504`
Do not blindly retry these:
- `400`
- `401`
- `403`
- `404`
- `409`
- `422`
For access control, always treat timeout and offline responses as failed unlocks unless your own access rules and safety processes say otherwise.
## Support details to include
When contacting support, include:
- Endpoint and HTTP method.
- Timestamp and timezone.
- Partner key ID, not the secret key.
- Facility identifier used.
- Source record ID from your system.
- Response status and body.
- `event_id` or `command_id` where available.
Source: https://docs.performancehub.co/api-docs/partner/errors
---
# Facilities
Represents a physical location, such as an office, store, venue, campus, clinic or gym, where members can access services.
### Endpoints
```
GET /partner/v1/facilities - List all facilities
```
Source: https://docs.performancehub.co/api-docs/partner/facilities
---
# List all facilities
Returns a list of facilities accessible to your partner account.
## Returns
Returns a JSON object with a `data` property containing an array of Facility objects.
### Request
```bash
curl https://partner-api.performancehub.co/partner/v1/facilities \
-u sk_live_YOUR_PARTNER_SECRET_KEY:
```
### Response
```json
{
"data": [
{
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
"org_id": "00000000-0000-0000-0000-000000000050",
"name": "Harbourside Central",
"brand": "Harbourside",
"website_url": "https://example.com/central",
"place_data": {
"country": {
"iso_code": "AU",
"full_name": "Australia"
},
"formatted_address": "123 Main Street, Sydney NSW 2000, Australia",
"google_place_id": "ChIJN1t_tDeuEmsRUsoyG83frY4",
"province": {
"abbreviation": "NSW",
"full_name": "New South Wales"
},
"city": {
"short_name": "Sydney",
"full_name": "Sydney"
}
},
"timezone": "Australia/Sydney"
}
]
}
```
Source: https://docs.performancehub.co/api-docs/partner/facilities/list
---
# The Facility object
## Attributes
- **facility_id** `string` required
Unique identifier for the facility.
- **org_id** `string` required
Unique identifier for the parent organisation.
- **name** `string` required
Facility name.
- **brand** `string` optional
Brand name used by the facility.
- **website_url** `string` optional
Facility website URL.
- **place_data** `object` optional
Location data from Google Places API containing country, formatted address, Google Place ID, province, and city.
- **social_links** `object` optional
Social media links for the facility.
- **timezone** `string` optional
IANA timezone identifier (e.g., `"Australia/Sydney"`, `"America/New_York"`).
### The Facility object
```json
{
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
"org_id": "00000000-0000-0000-0000-000000000050",
"name": "Harbourside Central",
"brand": "Harbourside",
"website_url": "https://example.com/central",
"social_links": {
"instagram": {
"handle": "@harbourside.central"
},
"facebook": {
"handle": "HarboursideCentral"
}
},
"place_data": {
"country": {
"iso_code": "AU",
"full_name": "Australia"
},
"formatted_address": "123 Main Street, Suite 100, Sydney NSW 2000, Australia",
"google_place_id": "ChIJN1t_tDeuEmsRUsoyG83frY4",
"province": {
"abbreviation": "NSW",
"full_name": "New South Wales"
},
"city": {
"short_name": "Sydney",
"full_name": "Sydney"
}
},
"timezone": "Australia/Sydney"
}
```
Source: https://docs.performancehub.co/api-docs/partner/facilities/object
---
# Going-Live Checklist
Use this checklist before enabling a partner integration for production facilities.
## Partner Console setup
- Your partner profile is complete.
- Your logo URL, website, category, and description are accurate.
- Your data linking method is configured: Direct Link or Linking Identifier.
- Facility linking has been tested with at least one real facility.
- The right key type is being used:
- **Dryrun** for non-destructive write testing.
- **Read-only** for safe data consumption.
- **Live** only when you are ready to affect production data.
## Authentication and key management
- Secret keys are stored server-side only.
- Keys are not embedded in client-side applications, mobile apps, public repositories, or support tickets.
- Key rotation and revocation have been tested.
- Your team knows which key is used by each environment.
## Data validation
- Required fields are present for every endpoint you call.
- Facility identifiers match your configured linking method.
- IDs from your system are stable and do not change between syncs.
- Large imports are split into batches under the documented limits.
- Failed batch items can be identified and resent.
## Endpoint testing
Before launch, test every endpoint your integration depends on:
- Organisations and facilities can be read.
- Members can be saved and updated.
- Membership plans, access zones, and access hours sync correctly.
- Check-ins are accepted and can be correlated back to your system.
- Report snapshots use the expected reporting period and metrics.
- Door/Gate Controller flows are tested on real hardware where applicable.
## Access Time Window readiness
If the integration supplies Access Time Windows:
- Absolute and weekly examples pass the [producer certification checklist](#members-access-time-windows).
- Epochs are unquoted integer Unix seconds; exact `[from, to)` boundaries have been tested.
- UTC defaults, the integration's custom IANA timezones, and relevant daylight-saving transitions have been verified.
- One durable, monotonic member/source revision stream coordinates incremental updates, reconciliation, and retries.
- Operators understand omitted, complete-replacement, idempotent, conflicting, stale, and explicit-`[]` behavior.
- Every `scope_key`, including `default`, has an approved Performance Hub mapping; unknown keys have been tested to fail closed.
- The integration's expected refresh cadence, maximum source age, stale-source behavior, and alerts are configured.
- An upstream fetch failure has been proven not to emit an empty or partial snapshot.
- Stage stream convergence, cancellation/revocation, and physical-door start/end boundaries meet the agreed targets.
- Regular rules and permanent grants have been verified as fallback paths, and windows cannot bypass user disablement, `blockAllAccess`, maintenance, or geofencing.
## Access control readiness
For access control integrations:
- Your system makes the access decision before calling Performance Hub.
- The integration handles `device_offline`, `maintenance_mode_active`, and `unlock_confirmation_timeout`.
- Operators understand that a timeout means the unlock was not confirmed.
- Your logs store the returned `event_id` and `command_id`.
- You have a manual fallback process for facility staff.
## Error handling and retries
- `400`, `401`, `403`, `404`, `409`, and `422` responses are surfaced to operators or support teams rather than retried blindly.
- `429`, `500`, `502`, `503`, and `504` responses use bounded retry logic with backoff.
- Repeated failures create an alert in your system.
- The integration never drops failed records silently.
## Observability
- Your logs include endpoint, timestamp, facility identifier, source record ID, request outcome, and response body.
- Your support team can search by member ID, facility ID, check-in ID, or unlock event ID.
- You have a way to replay or reconcile failed syncs.
## Production launch
- Performance Hub has confirmed your production access.
- At least one production facility has enabled your integration.
- Your production key is active.
- Your operational and technical contacts are current.
- Your support process is documented for customers.
After these checks are complete, your configured integration will appear in the Performance Hub partner directory inside the core Performance Hub platform.
Source: https://docs.performancehub.co/api-docs/partner/going-live
---
# Introduction
The Performance Hub Partner API enables external partner platforms to seamlessly integrate with Performance Hub's core modules. The API supports computer vision, analytics, member access logging, and facility insights.
## Request Format
The API accepts JSON-encoded request bodies and returns JSON-encoded responses.
## Rate Limiting
To ensure fair usage and system stability, Partner API accounts start with default limits:
- **5 requests per second** per endpoint
- **Recommended: 1000 items maximum** per batch request for optimal performance
- **10MB maximum** payload size
Rate limits are adjustable and can be configured on a case-by-case basis by your Performance Hub partner account manager. If your integration needs higher throughput for launch, migrations, reporting backfills, or high-volume facilities, raise this during onboarding or before the planned change window.
### Base URL
```
https://partner-api.performancehub.co/partner/v1/
```
Source: https://docs.performancehub.co/api-docs/partner/intro
---
# Members
Represents an individual customer of a facility. Can be a lead, prospect, recurring customer, expired member, or blocked user.
Member upserts can also carry a complete, revisioned `access_time_windows` snapshot containing absolute and recurring weekly access. See [Access Time Windows](#members-access-time-windows) before producing these fields. They are additive permissions and do not replace the facility's regular rules or permanent grants.
### Endpoints
```
POST /partner/v1/members - Save a member
```
Source: https://docs.performancehub.co/api-docs/partner/members
---
# Access Time Windows
Access Time Windows let a Partner Member carry temporary or recurring access supplied by an external system. This page is the authoritative producer guide for `access_time_windows` and `access_time_windows_revision`.
Access Time Windows are additive allow-only permissions. An active window can allow access, but an inactive, expired, stale, or unmapped window does not deny access that a regular rule or permanent grant already allows. User disablement, explicit door blocking, maintenance mode, geofencing, rate limits, and controller availability still apply.
Implementations for particular upstream systems are separate adapters. They must translate their source data into this vendor-neutral contract without changing its field meanings.
## Member snapshot fields
- **access_time_windows** `array` optional
The complete Access Time Window snapshot for this member and partner source. A snapshot can mix `absolute` and `weekly` windows. Omit the field to leave the stored snapshot unchanged; send `[]` to revoke every window owned by this source.
- **access_time_windows_revision** `integer` required with access_time_windows
A monotonically increasing, non-negative JSON-safe integer for this member/source revision stream. It is required whenever `access_time_windows` is present and must not exceed `9007199254740991`.
Each snapshot accepts at most 50 windows. `external_id`, `scope_key`, and `label` accept at most 120 characters. Absolute windows can span at most 90 days. A weekly day accepts at most eight non-overlapping blocks. Invalid data is rejected rather than truncated or guessed.
## Window shapes
An absolute window identifies one exact interval:
```json
{
"external_id": "access-4b1782",
"kind": "absolute",
"from": 1792053000,
"to": 1792058400,
"timezone": "Australia/Perth",
"scope_key": "after_hours_entry",
"label": "Temporary site access"
}
```
Required fields are `external_id`, `kind`, `from`, and `to`. `timezone`, `scope_key`, and `label` are optional.
A weekly window identifies recurring local wall-clock intervals, optionally bounded by UTC instants:
```json
{
"external_id": "weekly-early-access",
"kind": "weekly",
"timezone": "America/New_York",
"access_hours": {
"sun": [],
"mon": [["06:00", "08:30"]],
"tue": [["06:00", "08:30"]],
"wed": [["06:00", "08:30"]],
"thu": [["06:00", "08:30"]],
"fri": [["06:00", "08:30"]],
"sat": []
},
"active_from": 1790812800,
"active_until": 1796083200,
"scope_key": "main_entry",
"label": "Weekday early access"
}
```
Required fields are `external_id`, `kind`, and `access_hours`. `timezone`, `active_from`, `active_until`, `scope_key`, and `label` are optional.
## Complete mixed request
This request replaces the member's previous source-owned snapshot with one absolute and one weekly window:
```bash
curl -X POST https://partner-api.performancehub.co/partner/v1/members \
-u sk_live_YOUR_PARTNER_SECRET_KEY: \
-H "Content-Type: application/json" \
-d '{
"members": [
{
"member_id": "member_11111",
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
"first_name": "John",
"last_name": "Smith",
"access_time_windows_revision": 1842,
"access_time_windows": [
{
"external_id": "access-4b1782",
"kind": "absolute",
"from": 1792053000,
"to": 1792058400,
"timezone": "Australia/Perth",
"scope_key": "after_hours_entry",
"label": "Temporary site access"
},
{
"external_id": "weekly-early-access",
"kind": "weekly",
"timezone": "America/New_York",
"access_hours": {
"sun": [],
"mon": [["06:00", "08:30"]],
"tue": [["06:00", "08:30"]],
"wed": [["06:00", "08:30"]],
"thu": [["06:00", "08:30"]],
"fri": [["06:00", "08:30"]],
"sat": []
},
"active_from": 1790812800,
"active_until": 1796083200,
"scope_key": "main_entry",
"label": "Weekday early access"
}
]
}
]
}'
```
Use either `facility_id` or `linking_identifier` according to your configured linking method. Do not send both.
## Time and boundary rules
`from`, `to`, `active_from`, and `active_until` are JSON integer Unix seconds identifying UTC instants:
- Send an unquoted JSON integer. Numeric strings, floats, RFC 3339 strings, and Unix milliseconds are rejected.
- Every interval is half-open: `[from, to)`. Access can start exactly at `from`; it is no longer active exactly at `to`.
- `from` must be less than `to`. When both weekly bounds are present, `active_from` must be less than `active_until`.
- Do not derive epochs from an unzoned local date/time. First resolve the source time to a UTC instant, then divide Unix milliseconds by 1000 and verify the result is an integer.
### Epoch conversion
`2026-10-15T08:30:00Z` converts to `1792053000`:
```text
UTC instant Unix milliseconds Unix seconds sent
2026-10-15T08:30:00Z 1792053000000 1792053000
```
Language-neutral conversion:
```text
instant = parse_source_timestamp_with_explicit_zone(source_timestamp)
milliseconds = instant.to_unix_milliseconds()
assert milliseconds modulo 1000 equals 0
seconds = milliseconds / 1000
assert seconds is a JSON-safe nonnegative integer
```
If the source has sub-second precision, define a deliberate start/end rounding policy before integration testing. Never pass milliseconds and rely on the API to infer the unit.
## Timezones and daylight saving
`timezone` must be a valid IANA timezone such as `UTC`, `Australia/Perth`, or `America/New_York`. It defaults to `UTC` when omitted.
- **Absolute windows:** `from` and `to` already identify exact UTC instants. `timezone` is display and audit metadata only; changing it never shifts either instant.
- **Weekly windows:** `timezone` defines the local wall clock and daylight-saving rules used to evaluate `access_hours`. Do not pre-convert weekly blocks to UTC.
- During a spring-forward change, a local time that the timezone skips never occurs.
- During a fall-back change, both real instants carrying the repeated local time match the weekly block.
- `active_from` and `active_until` remain UTC instants even when a weekly window uses a non-UTC timezone.
`access_hours` uses lowercase `sun` through `sat`. Each value is an array of `[start, end]` pairs using zero-padded, 24-hour `HH:mm`. Blocks must be non-empty, ordered, and non-overlapping. `end` is exclusive. A block cannot cross midnight; split an overnight source interval into a late block on the first day and an early block on the next day.
## Stable IDs and scope mapping
`external_id` must be stable for the lifetime of a logical window and unique within the member snapshot. Keep it stable when times or labels change so operators and audit records can correlate revisions. Do not reuse it for an unrelated window.
`scope_key` is your stable logical access target. It defaults to `default`. A facility administrator maps each allowed key to Performance Hub facility-wide, door, or Access Scope targets:
```text
Partner scope_key Performance Hub mapping
default Facility's configured default target
main_entry Access Scope "Main entrances"
after_hours_entry Doors approved for after-hours entry
```
Partners do not send Performance Hub facility, door, or Access Scope IDs inside a window. An unknown, disabled, or unmapped `scope_key` fails closed for that window; it does not broaden access. Agree and test mappings before launch.
## Complete snapshots and revisions
Treat the two fields as a separately versioned partition within the Member upsert:
| Input | Result |
|-------|--------|
| `access_time_windows` omitted | Existing snapshot remains unchanged; no revision is required |
| Present with a newer revision | Entire stored snapshot for this member/source is replaced |
| Same revision and same normalized payload | Idempotent replay; no snapshot change |
| Same revision and different normalized payload | Conflict; allocate a new revision after reconciling |
| Older revision | Stale snapshot is skipped; it cannot overwrite newer state |
| `access_time_windows: []` with a newer revision | Explicitly revokes all source-owned windows |
A snapshot is complete, not a patch. If revision `1842` has windows A and B, then revision `1843` containing only B removes A.
Maintain one coordinated revision stream per partner/member across webhook workers, polling, replay, and full reconciliation. A durable database sequence is preferred. A Unix-millisecond value can be used as the revision only when every writer coordinates and guarantees strict ordering and uniqueness; it is a revision value, not an epoch field.
Persist the exact request and revision until the API response is known. Retry the same normalized snapshot with the same revision after a timeout or transient error. Do not allocate a new revision merely because transport delivery is uncertain.
## Cancellation, expiry, and upstream failures
- **Cancel one window:** successfully fetch authoritative source state, remove that window, and send the remaining complete snapshot with a newer revision.
- **Cancel all windows:** send `access_time_windows: []` with a newer revision.
- **Let an absolute window expire:** it stops allowing at `to`; send a later reconciliation without the expired entry to keep snapshots clean.
- **End a weekly window:** remove it in a newer snapshot, or use `active_until` when the source has a known end instant.
- **Source fetch fails or is incomplete:** do not send `[]` and do not publish a partial snapshot. Alert and retry the fetch. Turning an upstream failure into an empty snapshot is an unintended access revocation.
- **Partner API request fails validation:** correct the data and send a newer revision only if the normalized snapshot changes. Retry transient failures with the original revision.
Source freshness is configured per integration from its promised delivery and reconciliation cadence. Before activation, agree the maximum source age, what happens when it is exceeded, alert thresholds, and whether operators may acknowledge relying only on each window's own bounds. A stale source window never becomes a deny rule; regular rules and permanent grants remain fallback paths.
## Producer algorithm
Use this language-neutral workflow for every member update:
```text
function publish_member(member_key):
source_result = fetch_authoritative_access_state(member_key)
if source_result.failed or source_result.incomplete:
record_failure(member_key)
schedule_retry(member_key)
return without sending access_time_windows
windows = []
for source_window in source_result.current_windows:
if source_window.is_absolute:
windows.append({
external_id: stable_window_id(source_window),
kind: "absolute",
from: to_integer_unix_seconds(source_window.start_instant),
to: to_integer_unix_seconds(source_window.end_instant),
timezone: source_window.display_timezone or "UTC",
scope_key: stable_scope_key(source_window) or "default",
label: safe_label(source_window)
})
else:
windows.append({
external_id: stable_window_id(source_window),
kind: "weekly",
timezone: valid_iana_zone(source_window.timezone) or "UTC",
access_hours: split_validate_and_sort_weekly_blocks(source_window),
active_from: optional_integer_unix_seconds(source_window.active_start),
active_until: optional_integer_unix_seconds(source_window.active_end),
scope_key: stable_scope_key(source_window) or "default",
label: safe_label(source_window)
})
normalized_snapshot = validate_sort_and_deduplicate(windows)
revision = next_durable_member_revision(member_key)
request = member_upsert_with(normalized_snapshot, revision)
persist_for_retry(member_key, request)
response = post_request(request)
if response.is_transient_or_unknown:
retry_exact_request_with_same_revision(request)
else if response.is_revision_conflict:
reconcile_with_latest_committed_source_state(member_key)
else if response.is_stale_skip:
stop_replaying_this_revision_and_reconcile(member_key)
```
## Certification checklist
Complete these checks in a stage facility using the final `scope_key` mapping:
- [ ] An absolute window is accepted with omitted timezone and is evaluated as UTC.
- [ ] A custom-IANA absolute window preserves the same `from`/`to` instants.
- [ ] A weekly custom-IANA window follows local wall time across spring-forward and fall-back transitions.
- [ ] A mixed absolute/weekly snapshot replaces the prior snapshot completely.
- [ ] Access starts exactly at `from` and stops exactly at `to`; weekly block ends are also exclusive.
- [ ] Updating times under the same `external_id` with a newer revision takes effect.
- [ ] Omitting `access_time_windows` changes other Member fields without changing windows.
- [ ] Replaying the same revision/payload is idempotent.
- [ ] Same revision/different payload returns a conflict and does not change the snapshot.
- [ ] An older out-of-order revision is reported stale and cannot roll state back.
- [ ] A newer `[]` revokes source-owned windows; an upstream fetch failure sends nothing.
- [ ] An unknown or unmapped `scope_key` fails closed and creates an operator-visible warning.
- [ ] Source-age alerts and the configured stale-source policy work at the agreed threshold.
- [ ] Webhook and full reconciliation paths converge on the same snapshot and revision order.
- [ ] Stream delivery reaches Door & Gate Access within the agreed target.
- [ ] An active window can allow the mapped physical door, but cannot bypass user disablement, `blockAllAccess`, maintenance, or geofencing.
- [ ] Before, outside, and after a window, regular rules and permanent grants still allow users who otherwise qualify.
### Minimum test vectors
```json
[
{
"name": "absolute-half-open",
"window": {
"external_id": "tv-absolute",
"kind": "absolute",
"from": 1792053000,
"to": 1792058400
},
"expect": {
"1792052999": "inactive",
"1792053000": "active",
"1792058399": "active",
"1792058400": "inactive"
}
},
{
"name": "weekly-dst",
"window": {
"external_id": "tv-weekly-dst",
"kind": "weekly",
"timezone": "America/New_York",
"access_hours": {
"sun": [["01:00", "03:30"]],
"mon": [],
"tue": [],
"wed": [],
"thu": [],
"fri": [],
"sat": []
}
},
"expect": "Evaluate local wall time: skipped times do not occur; both repeated 01:30 instants qualify."
}
]
```
Also submit negative vectors for Unix milliseconds, numeric strings, floats, invalid IANA zones, `from >= to`, duplicate `external_id`, overlapping blocks, overnight blocks, more than eight blocks in a day, more than 50 windows, and unmapped scope keys.
## Related guidance
- [The Member object](#members-object)
- [Save a member](#members-save)
- [Best Practices](#best-practices)
- [Errors](#errors)
- [Going-Live Checklist](#going-live)
Source: https://docs.performancehub.co/api-docs/partner/members/access-time-windows
---
# The Member object
## Attributes
- **member_id** `string` required
Unique identifier for the member.
- **facility_id** `string` either/orProvide the facility identifier type configured for your integration: use facility_id for direct-link integrations, or linking_identifier for linking-identifier integrations. Do not send both; the API uses the identifier type configured for your partner account and ignores the other one. Learn more
Unique identifier of the facility from Performance Hub.
- **linking_identifier** `string` either/orProvide the facility identifier type configured for your integration: use facility_id for direct-link integrations, or linking_identifier for linking-identifier integrations. Do not send both; the API uses the identifier type configured for your partner account and ignores the other one. Learn more
Unique identifier value for each location that the partner uses (e.g., "Branch ID", "Location Code", "Store Number"). [Learn more](https://docs.performancehub.co/api-docs/partner-console#partner-linking-methods)
- **first_name** `string` required
Member's first name.
- **last_name** `string` required
Member's last name.
- **email** `string` optional
Email address.
- **mobile_phone** `string` optional
Mobile phone number in E.123 format (e.g., `"+1 415 555 2671"`).
- **address** `object` optional
Physical address object with the following properties:
- `address_line_1` (string) - Primary street address
- `address_line_2` (string, optional) - Secondary address line
- `city` (string) - City name
- `state` (string) - State/province code
- `postal_code` (string) - Postal/ZIP code
- `country` (string) - ISO 3166-1 alpha-2 country code (e.g., "AU", "US")
- `country_name` (string, optional) - Full country name
- **date_of_birth** `string` optional
Birth date in `YYYY-MM-DD` format.
- **gender** `string` optional
One of `male`, `female`, `non_binary`, `prefer_not_to_say`, or `unknown`.
- **type** `string` optional
Member type (e.g. `staff`, `member`, `prospect`).
- **tags** `array` optional
Quick helper strings or texts to categorise and identify specific members (e.g. `["vip", "personal-training", "student"]`).
- **membership_status** `string` optional
Current membership status (e.g., `active`, `expired`, `on_hold`).
- **membership_plans** `array` optional
Array of membership plans associated with the member.
- **profile_picture** `string` optional
Base64-encoded profile picture (optional).
- **created** `string` optional
ISO 8601 timestamp when the member was created.
- **updated** `string` optional
ISO 8601 timestamp when the member was last updated.
- **access_time_windows** `array` optional
Complete source-owned snapshot of absolute and/or recurring weekly Access Time Windows. Omit the field to leave the snapshot unchanged; send an empty array with a newer revision to revoke it. See [Access Time Windows](#members-access-time-windows) for the shapes, validation, scope mapping, and replacement semantics.
- **access_time_windows_revision** `integer` required with access_time_windows
Monotonically increasing, non-negative JSON-safe integer for this member/source stream. Required whenever `access_time_windows` is present.
### The Member object
```json
{
"member_id": "member_11111",
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
// "linking_identifier": "branch_downtown", // If using linking identifier
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@example.com",
"mobile_phone": "+61 412 345 678",
"date_of_birth": "1985-06-15",
"gender": "male",
"type": "member",
"tags": ["vip", "personal-training"],
"profile_picture": "/9j/4AAQSkZJRgABAQAAAQ...",
"membership_status": "current",
"created": "2024-01-15T10:30:00Z",
"updated": "2024-01-20T14:45:00Z",
"access_time_windows_revision": 1842,
"access_time_windows": [
{
"external_id": "access-4b1782",
"kind": "absolute",
"from": 1792053000,
"to": 1792058400,
"timezone": "Australia/Perth",
"scope_key": "after_hours_entry",
"label": "Temporary site access"
}
],
"address": {
"address_line_1": "456 Bourke Street",
"address_line_2": "Apt 12",
"city": "Melbourne",
"state": "VIC",
"postal_code": "3000",
"country": "AU",
"country_name": "Australia"
},
"membership_plans": [
{
"membership_plan_id": "plan_99999",
"name": "Unlimited Monthly",
"start_date": "2024-01-01T00:00:00Z",
"status": "current",
"is_current": true,
"end_date": null
}
]
}
```
Source: https://docs.performancehub.co/api-docs/partner/members/object
---
# Save a member
Creates or updates member records. The API determines whether to create or update based on the `member_id`.
## Parameters
- **members** `array` required
Array of Member objects to save. Recommended maximum: 1000 items per request for optimal performance. The API automatically batches items for processing. Each item must include either `facility_id` (required if not using `linking_identifier`) or `linking_identifier` (required if not using `facility_id`).
When a member includes `access_time_windows`, it must also include `access_time_windows_revision`. The array is a complete source snapshot, not a patch. Omit it to preserve the existing snapshot, or send `[]` with a newer revision to revoke all source-owned windows. Read the [Access Time Windows guide](#members-access-time-windows) before enabling this field.
## Returns
Returns a simple message confirming the sync operation.
Member profile fields remain normal upserts even when a window snapshot is reported as an idempotent replay or stale skip. A conflicting same-revision/different-payload snapshot is not retryable unchanged; reconcile source state and send a newer revision.
### Request
```bash
curl -X POST https://partner-api.performancehub.co/partner/v1/members \
-u sk_live_YOUR_PARTNER_SECRET_KEY: \
-H "Content-Type: application/json" \
-d '{
"members": [
{
"member_id": "member_11111",
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
// "linking_identifier": "branch_downtown", // If using linking identifier
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@example.com",
"mobile_phone": "+61 412 345 678",
"date_of_birth": "1985-06-15",
"gender": "male",
"type": "member",
"tags": ["vip", "personal-training"],
"profile_picture": "/9j/4AAQSkZJRgABAQAAAQ...",
"membership_status": "current",
"created": "2024-01-15T10:30:00Z",
"updated": "2024-01-20T14:45:00Z",
"access_time_windows_revision": 1842,
"access_time_windows": [
{
"external_id": "access-4b1782",
"kind": "absolute",
"from": 1792053000,
"to": 1792058400,
"timezone": "Australia/Perth",
"scope_key": "after_hours_entry",
"label": "Temporary site access"
}
],
"address": {
"address_line_1": "456 Bourke Street",
"address_line_2": "Apt 12",
"city": "Melbourne",
"state": "VIC",
"postal_code": "3000",
"country": "AU",
"country_name": "Australia"
},
"membership_plans": [
{
"membership_plan_id": "plan_99999",
"name": "Unlimited Monthly",
"start_date": "2024-01-01T00:00:00Z",
"status": "current",
"is_current": true,
"end_date": null
}
]
}
]
}'
```
### Response
Returns a success message with metadata about the sync operation.
```json
{
"message": "Member sync successful.",
"metadata": {
"success": 245,
"skipped": 5,
"skipped_items": [
{
"member_id": "member_123",
"facility_id": null,
"reason": "Facility integration is disabled in facility-partner-config"
}
]
}
}
```
**Metadata fields:**
- **success** - Number of members successfully processed
- **skipped** - Number of members skipped (if any)
- **skipped_items** - Array of skipped members with reasons (if any)
- **dry-run** - Boolean flag if using dryrun key type (if applicable)
Source: https://docs.performancehub.co/api-docs/partner/members/save
---
# Membership Plans
Defines a purchasable offering from an organisation or facility, including services, entitlements, schedule, and pricing.
### Endpoints
```
POST /partner/v1/membership-plans - Save a membership plan
```
Source: https://docs.performancehub.co/api-docs/partner/membership-plans
---
# Access Hours
Access hours specify when members can access a specific zone during each day of the week. Access hours are defined per [access zone](#membership-plans-access-zones), allowing different time restrictions for different areas of a facility.
### Structure
```json
{
"mon": [["start_time", "end_time"]],
"tue": [["start_time", "end_time"]],
"wed": [["start_time", "end_time"]],
"thu": [["start_time", "end_time"]],
"fri": [["start_time", "end_time"]],
"sat": [["start_time", "end_time"]],
"sun": [["start_time", "end_time"]]
}
```
Each day is an array of time ranges in `HH:MM` format (24-hour clock). An empty array `[]` means no access on that day. Days that are omitted are treated the same as an empty array.
### Example Use Cases
**Office tenant – business hours:** access during business hours on weekdays only.
```json
{
"access_hours": {
"mon": [["07:00", "19:00"]],
"tue": [["07:00", "19:00"]],
"wed": [["07:00", "19:00"]],
"thu": [["07:00", "19:00"]],
"fri": [["07:00", "19:00"]]
}
}
```
**Student – term access:** a campus library open on weekdays, with a break in the middle of the day, and on Saturday mornings.
```json
{
"access_hours": {
"mon": [["08:00", "12:30"], ["13:30", "21:00"]],
"tue": [["08:00", "12:30"], ["13:30", "21:00"]],
"wed": [["08:00", "12:30"], ["13:30", "21:00"]],
"thu": [["08:00", "12:30"], ["13:30", "21:00"]],
"fri": [["08:00", "12:30"], ["13:30", "17:00"]],
"sat": [["09:00", "13:00"]]
}
}
```
**Gym – early bird:** morning-only access on weekdays.
```json
{
"access_hours": {
"mon": [["04:00", "12:00"]],
"tue": [["04:00", "12:00"]],
"wed": [["04:00", "12:00"]],
"thu": [["04:00", "12:00"]],
"fri": [["04:00", "12:00"]]
}
}
```
**Hotel guest – 24/7:** always-on access, the most common use case.
```json
{
"access_hours": {
"mon": [["00:00", "23:59"]],
"tue": [["00:00", "23:59"]],
"wed": [["00:00", "23:59"]],
"thu": [["00:00", "23:59"]],
"fri": [["00:00", "23:59"]],
"sat": [["00:00", "23:59"]],
"sun": [["00:00", "23:59"]]
}
}
```
### Per-Zone Example
A hotel guest plan with round-the-clock access to the guest floors and shorter hours for the pool:
```json
{
"access_zones": [
{
"name": "Guest Floors",
"description": "Lifts and corridors on the guest room levels",
"access_hours": {
"mon": [["00:00", "23:59"]],
"tue": [["00:00", "23:59"]],
"wed": [["00:00", "23:59"]],
"thu": [["00:00", "23:59"]],
"fri": [["00:00", "23:59"]],
"sat": [["00:00", "23:59"]],
"sun": [["00:00", "23:59"]]
}
},
{
"name": "Swimming Pool",
"description": "Access to the swimming pool and change rooms",
"access_hours": {
"mon": [["06:00", "20:00"]],
"tue": [["06:00", "20:00"]],
"wed": [["06:00", "20:00"]],
"thu": [["06:00", "20:00"]],
"fri": [["06:00", "20:00"]],
"sat": [["08:00", "18:00"]],
"sun": [["08:00", "18:00"]]
}
}
]
}
```
Source: https://docs.performancehub.co/api-docs/partner/membership-plans/access-hours
---
# Access Zones
Access zones define specific areas within a facility where a membership plan grants access. Each zone can have its own access hours, allowing different time restrictions per area.
## Structure
Each access zone includes:
- **name** `string` required
The name of the zone (e.g., "Level 3 Tenancy", "Library", "Guest Floors", "Main Gym Floor").
- **description** `string` optional
Description of what's included in that zone.
- **access_hours** `object` optional
Access hours specific to this zone. See [Access Hours](#membership-plans-access-hours) for the time range format.
### Example
An office building with a gym for its tenants: access to the tenancy during business hours, and to the gym from early morning to late evening.
```json
{
"access_zones": [
{
"name": "Level 3 Tenancy",
"description": "Lift lobby, offices and meeting rooms on level 3",
"access_hours": {
"mon": [["07:00", "19:00"]],
"tue": [["07:00", "19:00"]],
"wed": [["07:00", "19:00"]],
"thu": [["07:00", "19:00"]],
"fri": [["07:00", "19:00"]]
}
},
{
"name": "Tenant Gym",
"description": "Ground-floor gym and change rooms for building tenants",
"access_hours": {
"mon": [["05:00", "22:00"]],
"tue": [["05:00", "22:00"]],
"wed": [["05:00", "22:00"]],
"thu": [["05:00", "22:00"]],
"fri": [["05:00", "22:00"]],
"sat": [["07:00", "18:00"]],
"sun": [["07:00", "18:00"]]
}
}
]
}
```
Source: https://docs.performancehub.co/api-docs/partner/membership-plans/access-zones
---
# The Membership Plan object
## Attributes
- **membership_plan_id** `string` required
Unique identifier for the membership plan.
- **facility_id** `string` either/orProvide the facility identifier type configured for your integration: use facility_id for direct-link integrations, or linking_identifier for linking-identifier integrations. Do not send both; the API uses the identifier type configured for your partner account and ignores the other one. Learn more
Unique identifier of the facility from Performance Hub.
- **linking_identifier** `string` either/orProvide the facility identifier type configured for your integration: use facility_id for direct-link integrations, or linking_identifier for linking-identifier integrations. Do not send both; the API uses the identifier type configured for your partner account and ignores the other one. Learn more
Unique identifier value for each location that the partner uses (e.g., "Branch ID", "Location Code", "Store Number"). [Learn more](https://docs.performancehub.co/api-docs/partner-console#partner-linking-methods)
- **name** `string` required
Human-readable plan name (e.g., `"Office tenant – business hours"`, `"Student – term access"`, `"Hotel guest"`, `"10 Class Pack"`).
- **billing_cycle** `string` optional
Billing frequency. One of `weekly`, `fortnightly`, `monthly`, `annually`, or `one_time`.
- **price** `integer` optional
Base cost in smallest currency unit (cents for USD/AUD).
- **currency** `string` optional
ISO 4217 currency code (e.g., `"AUD"`, `"USD"`).
- **description** `string` optional
Optional marketing or internal description.
- **active_members** `integer` optional
Number of active members using this membership plan
- **is_recurring** `boolean` optional
Whether the plan auto-renews.
- **is_trial** `boolean` optional
Whether this is a trial offering.
- **visible_to_public** `boolean` optional
Whether the plan is available for self-service purchase.
- **duration_days** `integer` optional
Number of days the membership plan is valid for.
- **access_zones** `array` optional
Array of access zones included in the membership plan. Each zone can define its own access hours. See [Access Zones](#membership-plans-access-zones) for details.
- **class_limit** `integer` optional
Maximum number of classes allowed (null for unlimited).
### The Membership Plan object
This example uses `facility_id`. For a linking-identifier integration, send `"linking_identifier": "branch_downtown"` instead.
```json
{
"membership_plan_id": "plan_99999",
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Office tenant – business hours",
"description": "Weekday access to the level 3 tenancy and the end-of-trip facilities",
"billing_cycle": "monthly",
"price": 8900,
"currency": "AUD",
"duration_days": 30,
"is_recurring": true,
"is_trial": false,
"visible_to_public": false,
"active_members": 245,
"access_zones": [
{
"name": "Level 3 Tenancy",
"description": "Lift lobby, offices and meeting rooms on level 3",
"access_hours": {
"mon": [["07:00", "19:00"]],
"tue": [["07:00", "19:00"]],
"wed": [["07:00", "19:00"]],
"thu": [["07:00", "19:00"]],
"fri": [["07:00", "19:00"]]
}
},
{
"name": "End-of-trip Facilities",
"description": "Bike storage, lockers and showers",
"access_hours": {
"mon": [["06:00", "20:00"]],
"tue": [["06:00", "20:00"]],
"wed": [["06:00", "20:00"]],
"thu": [["06:00", "20:00"]],
"fri": [["06:00", "20:00"]]
}
}
],
"class_limit": null
}
```
Source: https://docs.performancehub.co/api-docs/partner/membership-plans/object
---
# Save a membership plan
Creates or updates membership plan records. The API determines whether to create or update based on the `membership_plan_id`.
## Parameters
- **membership_plans** `array` required
Array of Membership Plan objects to save. Recommended maximum: 1000 items per request for optimal performance. The API automatically batches items for processing. Each item must include either `facility_id` (required if not using `linking_identifier`) or `linking_identifier` (required if not using `facility_id`).
## Returns
Returns a simple message confirming the sync operation.
### Request
This example uses `facility_id`. For a linking-identifier integration, send `"linking_identifier": "branch_downtown"` instead.
```bash
curl -X POST https://partner-api.performancehub.co/partner/v1/membership-plans \
-u sk_live_YOUR_PARTNER_SECRET_KEY: \
-H "Content-Type: application/json" \
-d '{
"membership_plans": [
{
"membership_plan_id": "plan_99999",
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Student – term access",
"description": "Library and student hub access for one teaching term",
"billing_cycle": "one_time",
"price": 4500,
"currency": "AUD",
"duration_days": 84,
"is_recurring": false,
"is_trial": false,
"visible_to_public": true,
"active_members": 245,
"access_zones": [
{
"name": "Library",
"description": "Reading rooms, study spaces and bookable rooms",
"access_hours": {
"mon": [["08:00", "21:00"]],
"tue": [["08:00", "21:00"]],
"wed": [["08:00", "21:00"]],
"thu": [["08:00", "21:00"]],
"fri": [["08:00", "17:00"]],
"sat": [["09:00", "13:00"]]
}
},
{
"name": "Student Hub",
"description": "Common room, kitchen and lockers",
"access_hours": {
"mon": [["07:00", "22:00"]],
"tue": [["07:00", "22:00"]],
"wed": [["07:00", "22:00"]],
"thu": [["07:00", "22:00"]],
"fri": [["07:00", "22:00"]]
}
}
],
"class_limit": null
}
]
}'
```
### Response
Returns a success message with metadata about the sync operation.
```json
{
"message": "Membership plan sync successful.",
"metadata": {
"success": 98,
"skipped": 2,
"skipped_items": [
{
"membership_plan_id": "plan_456",
"facility_id": null,
"reason": "Missing facility_id"
}
]
}
}
```
**Metadata fields:**
- **success** - Number of membership plans successfully processed
- **skipped** - Number of plans skipped (if any)
- **skipped_items** - Array of skipped plans with reasons (if any)
- **dry-run** - Boolean flag if using dryrun key type (if applicable)
Source: https://docs.performancehub.co/api-docs/partner/membership-plans/save
---
# Organisations
Represents the top-level business entity that operates one or more facilities integrated with the Performance Hub platform.
### Endpoints
```
GET /partner/v1/organisations - List all organisations
```
Source: https://docs.performancehub.co/api-docs/partner/organisations
---
# List all organisations
Returns a list of organisations accessible to your partner account.
## Returns
Returns a JSON object with a `data` property containing an array of Organisation objects.
### Request
```bash
curl https://partner-api.performancehub.co/partner/v1/organisations \
-u sk_live_YOUR_PARTNER_SECRET_KEY:
```
### Response
```json
{
"data": [
{
"org_id": "00000000-0000-0000-0000-000000000050",
"name": "Harbourside Group"
},
{
"org_id": "6ba7b811-9dad-11d1-80b4-00c04fd430c8",
"name": "Wellness Studios Network"
}
]
}
```
Source: https://docs.performancehub.co/api-docs/partner/organisations/list
---
# The Organisation object
## Attributes
- **org_id** `string` required
Unique identifier for the organisation.
- **name** `string` required
Organisation name.
### The Organisation object
```json
{
"org_id": "00000000-0000-0000-0000-000000000050",
"name": "Harbourside Group"
}
```
Source: https://docs.performancehub.co/api-docs/partner/organisations/object
---
# Facility Reports
A point-in-time record capturing key operational and financial metrics for a facility during a specific period.
### Endpoints
```
POST /partner/v1/report-snapshots - Save report snapshots
```
Source: https://docs.performancehub.co/api-docs/partner/reports
---
# The Report Snapshot object
## Attributes
- **facility_id** `string` either/orProvide the facility identifier type configured for your integration: use facility_id for direct-link integrations, or linking_identifier for linking-identifier integrations. Do not send both; the API uses the identifier type configured for your partner account and ignores the other one. Learn more
Unique identifier of the facility from Performance Hub.
- **linking_identifier** `string` either/orProvide the facility identifier type configured for your integration: use facility_id for direct-link integrations, or linking_identifier for linking-identifier integrations. Do not send both; the API uses the identifier type configured for your partner account and ignores the other one. Learn more
Unique identifier value for each location that the partner uses (e.g., "Branch ID", "Location Code", "Store Number"). [Learn more](https://docs.performancehub.co/api-docs/partner-console#partner-linking-methods)
- **start_date** `string` required
Snapshot period start date in `YYYY-MM-DD` format.
- **end_date** `string` required
Snapshot period end date in `YYYY-MM-DD` format.
- **metrics** `object` required
Metrics object containing member and revenue data for the period.
### The Report Snapshot object
```json
{
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
// "linking_identifier": "branch_downtown", // If using linking identifier
"start_date": "2025-01-01",
"end_date": "2025-01-31",
"metrics": {
"members": {
"active": 1200,
"new": 45,
"cancelled": 12
},
"revenue": {
"total_payments": 289000.00,
"total_payments_count": 1200,
"currency": "AUD"
}
}
}
```
Source: https://docs.performancehub.co/api-docs/partner/reports/object
---
# Save report snapshots
Submits operational and financial metrics for a facility during a specific period.
## Parameters
- **snapshots** `array` required
Array of Report Snapshot objects to save. Maximum 25 items per request. Each item must include either `facility_id` (required if not using `linking_identifier`) or `linking_identifier` (required if not using `facility_id`).
## Returns
Returns a simple message confirming the sync operation.
### Request
```bash
curl -X POST https://partner-api.performancehub.co/partner/v1/report-snapshots \
-u sk_live_YOUR_PARTNER_SECRET_KEY: \
-H "Content-Type: application/json" \
-d '{
"snapshots": [
{
"facility_id": "550e8400-e29b-41d4-a716-446655440000",
// "linking_identifier": "branch_downtown", // If using linking identifier
"start_date": "2025-01-01",
"end_date": "2025-01-31",
"metrics": {
"members": {
"active": 1200,
"new": 45,
"cancelled": 12
},
"revenue": {
"total_payments": 289000.00,
"currency": "AUD"
}
}
}
]
}'
```
### Response
```json
{
"message": "Report snapshot sync successful."
}
```
Source: https://docs.performancehub.co/api-docs/partner/reports/save
---
# Versioning
The Partner API is versioned in the URL. The current version is:
```text
https://partner-api.performancehub.co/partner/v1/
```
## Change policy
Performance Hub aims to make additive, backward-compatible changes wherever possible.
Examples of backward-compatible changes:
- Adding a new optional field to a request.
- Adding a new field to a response.
- Adding a new endpoint.
- Adding a new enum value where clients are expected to handle unknown values.
- Improving validation messages without changing the status code contract.
`access_time_windows` and `access_time_windows_revision` are additive Member request fields. Existing clients that omit them keep the stored Access Time Window snapshot unchanged. This omission rule preserves partial-upsert compatibility; omission is not equivalent to sending an empty array.
Examples of breaking changes:
- Removing or renaming a field.
- Changing the type of a field.
- Making an optional request field required.
- Removing an endpoint.
- Changing the meaning of an existing status code or domain-specific `reason`.
## Deprecations
When a breaking change is required, Performance Hub will avoid changing the existing version in place where possible. Instead, we will introduce a new version or migration path and communicate the change to affected partners.
Deprecation notices can include:
- A changelog entry.
- Direct partner communication.
- Notes in the Partner Console.
- Migration guidance in the docs.
## Client recommendations
Build clients that tolerate additive changes:
- Ignore response fields you do not use.
- Treat unknown enum values as unsupported rather than crashing.
- Avoid strict response equality checks.
- Log unrecognized values so you can update your integration intentionally.
For revisioned Member partitions such as Access Time Windows, persist the submitted revision and normalized payload until the outcome is known. Reusing a revision for changed content is a conflict, while replaying the same content is idempotent. See [Access Time Windows](#members-access-time-windows).
## OpenAPI specifications
Use the published OpenAPI specifications to generate clients, validate requests, and track API shape changes over time.
See [OpenAPI Specifications](https://docs.performancehub.co/api-docs/openapi-specs).
Source: https://docs.performancehub.co/api-docs/partner/versioning