# Authentication

The Hero Health Public API uses API key authentication on all requests. Every API key is tied to a specific Hero practice group and partner. Scopes control which endpoints your key can access.

## Getting your API key

### Step 1: Join the Partner Programme

Before you can access Hero's APIs, you need to become a verified partner:

1. Fill out our partner onboarding form at our [Get Started page](https://www.herohealthsoftware.net/partner-program/get-started)
2. Review and sign our Partner Agreement (includes SLA and rate limits)
3. Receive access credentials for both staging and production environments


### Step 2: Access developer resources

Once approved, you'll receive:

- A **staging API key** for development and testing
- A **practice group ID** for each practice you're integrating with
- Access to our Slack environment for developer support


### Step 3: Secure your credentials

Keep your keys secret
Never expose API keys in client-side code or commit them to source control. Store keys securely using environment variables or a secrets manager. Use separate keys for staging and production.

## Making authenticated requests

All requests require two headers:

| Header | Description |
|  --- | --- |
| `x-api-key` | Your Hero Health API key |
| `x-practice-group-id` | The ID of the practice you are acting on behalf of |


```bash curl
curl -X GET "https://api.herohealth.net/v1/practice-groups" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-practice-group-id: YOUR_PRACTICE_GROUP_ID"
```

```js Node.js
const response = await fetch('https://api.herohealth.net/v1/practice-groups', {
  method: 'GET',
  headers: {
    'x-api-key': process.env.HERO_API_KEY,
    'x-practice-group-id': process.env.HERO_PRACTICE_GROUP_ID,
  },
});

if (!response.ok) {
  throw new Error(`Request failed: ${response.status}`);
}

const data = await response.json();
```

```ruby Ruby
require 'net/http'
require 'json'

uri = URI('https://api.herohealth.net/v1/practice-groups')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Get.new(uri)
request['x-api-key'] = ENV['HERO_API_KEY']
request['x-practice-group-id'] = ENV['HERO_PRACTICE_GROUP_ID']

response = http.request(request)
data = JSON.parse(response.body)
```

## Environments

| Environment | Base URL | Use for |
|  --- | --- | --- |
| **Staging** | `https://api.staging.htech.app` | Development, testing, and integration verification |
| **Production** | `https://api.herohealth.net` | Live applications serving real practices |


Use test data only in staging — it does not contain real patient information.

## Cross-organisational calls

Cross-org access
Cross-organisational calls allow you to make API requests on behalf of a different practice group, provided both practices belong to the same Hero-configured network.

Hero supports cross-organisational API calls. If the `x-practice-group-id` header you pass does not match the practice group ID configured on your API key, Hero will verify that the target practice exists within a Hero-configured network alongside your key's practice. If such a network exists, the call returns data from the specified practice group.

The primary use case is cross-organisational bookings — for example, a practice within a PCN inviting patients to book at a hub site.

### Cross-organisational call flow

```mermaid
sequenceDiagram
    participant Client as Your Server
    participant Hero as Hero API
    participant Network as Network Check
    Client->>Hero: API request<br/>x-api-key: YOUR_KEY<br/>x-practice-group-id: target-practice
    Hero->>Network: Verify practices share a network
    alt Network exists
        Network-->>Hero: Confirmed
        Hero-->>Client: 200 OK { data from target practice }
    else No network
        Network-->>Hero: Denied
        Hero-->>Client: 403 Forbidden
    end
```

## Authentication errors

| Status | Error | Cause |
|  --- | --- | --- |
| `401` | `invalid_credentials` | API key is missing, invalid, or expired |
| `403` | `insufficient_permissions` | Key does not have the required scope for this endpoint |
| `403` | `forbidden` | Cross-org access denied — practices are not in the same network |
| `429` | `rate_limit_exceeded` | Too many requests — implement retry with exponential backoff |


The API enforces a limit of **300 requests per minute** per API key. Contact [Hero support](mailto:support@herohealth.net) if your integration requires a higher limit.

## Security best practices

- Store API keys in environment variables or a secrets manager — never in source code
- Use separate keys for staging and production
- Request only the minimum scopes your integration needs
- All API calls must use HTTPS
- Contact Hero support to rotate a compromised key immediately


## Next steps

- [Basic concepts](/pages/api/get-started/basic-concepts) — understand Hero's data model
- [Your first API call](/pages/api/get-started/your-first-api-call) — make a working API request end-to-end