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/

Authentication

Access to the Partner API requires authentication using API secret keys. Generate and manage your keys through the 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

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

curl https://partner-api.performancehub.co/partner/v1/auth/ping \
  -u sk_live_YOUR_PARTNER_SECRET_KEY:
# Returns { "message": "You are successfully authorised as <PartnerName>" }

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

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

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

The Organisation object

Attributes

  • org_id string required

    Unique identifier for the organisation.

  • name string required

    Organisation name.

The Organisation object

{
  "org_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "name": "FitLife Fitness Group"
}

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

curl https://partner-api.performancehub.co/partner/v1/organisations \
  -u sk_live_YOUR_PARTNER_SECRET_KEY:

Response

{
  "data": [
    {
      "org_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
      "name": "FitLife Fitness Group"
    },
    {
      "org_id": "6ba7b811-9dad-11d1-80b4-00c04fd430c8",
      "name": "Wellness Studios Network"
    }
  ]
}

Facilities

Represents a physical location, branch, club, gym, or studio where members can access services.

Endpoints

GET /partner/v1/facilities - List all facilities

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

{
  "facility_id": "550e8400-e29b-41d4-a716-446655440000",
  "org_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "name": "FitLife Downtown",
  "brand": "FitLife",
  "website_url": "https://fitlife.com/downtown",
  "social_links": {
    "instagram": {
      "handle": "@fitlifedowntown"
    },
    "facebook": {
      "handle": "FitLifeDowntown"
    }
  },
  "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"
}

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

curl https://partner-api.performancehub.co/partner/v1/facilities \
  -u sk_live_YOUR_PARTNER_SECRET_KEY:

Response

{
  "data": [
    {
      "facility_id": "550e8400-e29b-41d4-a716-446655440000",
      "org_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
      "name": "FitLife Downtown",
      "brand": "FitLife",
      "website_url": "https://fitlife.com/downtown",
      "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"
    }
  ]
}

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

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

  • name string required

    Human-readable plan name (e.g., "Unlimited Monthly", "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 for details.

  • class_limit integer optional

    Maximum number of classes allowed (null for unlimited).

The Membership Plan object

{
  "membership_plan_id": "plan_99999",
  "facility_id": "550e8400-e29b-41d4-a716-446655440000",
  // "linking_identifier": "branch_downtown",  // If using linking identifier
  "name": "Unlimited Monthly",
  "description": "Unlimited access to all gym facilities and group classes",
  "billing_cycle": "monthly",
  "price": 8900,
  "currency": "AUD",
  "duration_days": 30,
  "is_recurring": true,
  "is_trial": false,
  "visible_to_public": true,
  "active_members": 245,
  "access_zones": [
    {
      "name": "Main Gym Floor",
      "description": "Full access to cardio and weight training equipment",
      "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": [["06:00", "20:00"]],
        "sun": [["07:00", "18:00"]]
      }
    },
    {
      "name": "Group Fitness Studio",
      "description": "Access to group fitness classes",
      "access_hours": {
        "sat": [["06:00", "20:00"]],
        "sun": [["07:00", "18:00"]]
      }
    }
  ],
  "class_limit": null
}

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., "Main Gym Floor", "Group Fitness Studio").

  • description string optional

    Description of what's included in that zone.

  • access_hours object optional

    Access hours specific to this zone. See Access Hours for the time range format.

Example

"access_zones": [
  {
    "name": "Main Gym Floor",
    "description": "Full access to cardio and weight training equipment",
    "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": [["06:00", "20:00"]],
      "sun": [["07:00", "18:00"]]
    }
  },
  {
    "name": "Group Fitness Studio",
    "description": "Access to group fitness classes",
    "access_hours": {
      "sat": [["06:00", "20:00"]],
      "sun": [["07:00", "18:00"]]
    }
  }
]

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, allowing different time restrictions for different areas of a facility.

Structure

{
  "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

// Early Bird Access
// Morning-only gym access, weekdays only:
"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"]]
}
// Off-Peak Plan
// Avoids peak hours, weekdays only:
"access_hours": {
  "mon": [["10:00", "12:00"], ["14:00", "16:00"]],
  "tue": [["10:00", "12:00"], ["14:00", "16:00"]],
  "wed": [["10:00", "12:00"], ["14:00", "16:00"]],
  "thu": [["10:00", "12:00"], ["14:00", "16:00"]],
  "fri": [["10:00", "12:00"], ["14:00", "16:00"]]
}
// Weekend Warrior
// Full-day access on weekends:
"access_hours": {
  "sat": [["08:00", "20:00"]],
  "sun": [["08:00", "20:00"]]
}
// 24/7 Access (Most common use case)
// Always-on access, unlimited:
"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

"access_zones": [
  {
    "name": "Main Gym Floor",
    "description": "Full access to cardio and weight training equipment",
    "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": [["06:00", "20:00"]],
      "sun": [["07:00", "18:00"]]
    }
  },
  {
    "name": "Swimming Pool",
    "description": "Access to 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"]]
    }
  }
]

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

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",
        // "linking_identifier": "branch_downtown",  // If using linking identifier
        "name": "Unlimited Monthly",
        "description": "Unlimited access to all gym facilities and group classes",
        "billing_cycle": "monthly",
        "price": 8900,
        "currency": "AUD",
        "duration_days": 30,
        "is_recurring": true,
        "is_trial": false,
        "visible_to_public": true,
        "active_members": 245,
        "access_zones": [
          {
            "name": "Main Gym Floor",
            "description": "Full access to cardio and weight training equipment",
            "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": [["06:00", "20:00"]],
              "sun": [["07:00", "18:00"]]
            }
          },
          {
            "name": "Group Fitness Studio",
            "description": "Access to group fitness classes",
            "access_hours": {
              "sat": [["06:00", "20:00"]],
              "sun": [["07:00", "18:00"]]
            }
          }
        ],
        "class_limit": null
      }
    ]
  }'

Response

Returns a success message with metadata about the sync operation.

{
  "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)

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

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

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

{
  "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@email.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
    }
  ]
}

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:

Complete mixed request

This request replaces the member's previous source-owned snapshot with one absolute and one weekly window:

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.

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:

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:

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.

Related guidance

{
  "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:

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

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.

Epoch conversion

2026-10-15T08:30:00Z converts to 1792053000:

UTC instant                 Unix milliseconds   Unix seconds sent
2026-10-15T08:30:00Z        1792053000000        1792053000

Language-neutral conversion:

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.

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.

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)

Minimum test vectors

[
  {
    "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.

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

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

{
  "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)

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

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

  • 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

{
  // "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"
}

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

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.

{
  "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)

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

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

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

  • 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

{
  "door_id": "577393ce6ec584aa4e2e2b1d1e73934c",
  "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"
}

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

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

{
  "data": [
    {
      "door_id": "577393ce6ec584aa4e2e2b1d1e73934c",
      "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"
    }
  ]
}

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

curl -X GET "https://partner-api.performancehub.co/partner/v1/access-control/doors/577393ce6ec584aa4e2e2b1d1e73934c?facility_id=550e8400-e29b-41d4-a716-446655440000" \
  -u sk_live_YOUR_PARTNER_SECRET_KEY:

Response

{
  "data": {
    "door_id": "577393ce6ec584aa4e2e2b1d1e73934c",
    "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"
  }
}

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

curl -X POST https://partner-api.performancehub.co/partner/v1/access-control/doors/577393ce6ec584aa4e2e2b1d1e73934c/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": "Smith",
    "full_name": "Jane Smith",
    "source": "door_reader",
    "idempotency_key": "unlock_20260501_0001"
  }'

Response

{
  "status": "unlocked",
  "door_id": "577393ce6ec584aa4e2e2b1d1e73934c",
  "facility_id": "550e8400-e29b-41d4-a716-446655440000",
  "event_id": "2d98e9ff-3a2a-46d4-a22f-5f23f7d75b11",
  "command_id": "9d4d013d-d929-4a87-8b52-6cc66f56ff07",
  "hold_time_seconds": 10,
  "confirmed_at": "2026-05-01T04:30:00.000Z"
}

Failure response

{
  "status": "failed",
  "reason": "device_offline",
  "door_id": "577393ce6ec584aa4e2e2b1d1e73934c",
  "facility_id": "550e8400-e29b-41d4-a716-446655440000",
  "event_id": "2d98e9ff-3a2a-46d4-a22f-5f23f7d75b11"
}

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

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

  • 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

{
  "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"
    }
  }
}

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

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

{
  "message": "Report snapshot sync successful."
}

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.

Status codes

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

{
  "status": "failed",
  "reason": "device_offline",
  "door_id": "577393ce6ec584aa4e2e2b1d1e73934c",
  "facility_id": "550e8400-e29b-41d4-a716-446655440000",
  "event_id": "2d98e9ff-3a2a-46d4-a22f-5f23f7d75b11"
}

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.

Versioning

The Partner API is versioned in the URL. The current version is:

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.

OpenAPI specifications

Use the published OpenAPI specifications to generate clients, validate requests, and track API shape changes over time.

See OpenAPI Specifications.

https://partner-api.performancehub.co/partner/v1/

Changelog

This changelog records notable Partner API and Partner Console changes that affect integration behaviour.

2026-09-09

2026-05-06

2026-05-01

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.

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.

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.

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.