# API Key Authentication

> Exchange a partner API key for a bearer token, including member-scoped tokens and refresh.

HTML version: [API Key Authentication](https://support.gladstonesoftware.cloud/guides/api-key-authentication)

> ⚠️
>
> **This feature is still in BETA**
>
> API authentication via API keys is still under development and in beta. This feature requires API access enabled on your environment. Usage of custom connectors is a usage billed product unless you have an API package. Please note any usage of this whilst in BETA stage is free but following GR release, any usage not part of an agreed package will be billed at standard usage rates.
>
> If you are an approved 3rd party connector, please reach out to Gladstone to discuss usage.

Token-based authentication for third-party web integrations. Replaces the cookie-based,
browser-bound model. If you're building a web integration with Gladstone APIs, you must
authenticate with an API key and bearer tokens — not cookies.

## Prerequisites

Before you start, you can generate a key within the Operator portal:

* An **API key** in the format `gsk_{version}_{secret}` — your integration's credential.
  Shown to you **exactly once** when issued. Store it securely; we cannot recover it.
* The **environment URL** for the tenant you are integrating against.
* A **registered Origin domain** (browser-based integrations only — see [Security notes](#security-notes)).

Keep the API key in a server-side secret store. **Do not embed it in browser JavaScript or any
client-side code.**

### Integration types

There are two types of API key integration:

| Type                     | Description                                                                                                                   | Pricing                      |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| **Marketplace Provider** | A pre-built integration listed in the Gladstone marketplace. Available for any Gladstone customer to activate for their site. | Fixed cost                   |
| **Custom API key**       | A bespoke integration scoped to a single Gladstone customer's site. Created and managed by the customer for their own use.    | Chargeable per key & request |

Marketplace
Providers are typically third-party software vendors with an existing onboarding agreement
with Gladstone, these come pre-packaged with locked down security and included API requests. Custom API keys are issued for customer-specific integrations and are chargeable as part of your API usage package.

## How authentication works

You exchange your API key for a short-lived JWT, then send that JWT as a `Bearer` token on
every subsequent API call.

```text
[Your backend] -- API key -->  POST /partner/token  -->  [JWT + refresh token]
[Your backend] -- Bearer JWT -->  any Gladstone API
```

## Step 1 — Get a token

`POST https://{environment}/api/samlauthentication/partner/token`

Always send:

| Header              | Value                                                      |
| ------------------- | ---------------------------------------------------------- |
| `X-Partner-Api-Key` | your API key (`gsk_{version}_{secret}`)                    |
| `Origin`            | your registered domain *(browser-based integrations only)* |
| `Content-Type`      | `application/json`                                         |

The request body decides the grant type:

### a) Machine-to-machine token (no member context)

```bash
curl -X POST 'https://{environment}/api/samlauthentication/partner/token' \
  -H 'X-Partner-Api-Key: gsk_sV_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  -H 'Origin: https://yourcompany.com' \
  -H 'Content-Type: application/json' \
  -d '{}'
```

### b) Member-scoped token (acting on behalf of a member)

```bash
curl -X POST 'https://{environment}/api/samlauthentication/partner/token' \
  -H 'X-Partner-Api-Key: gsk_sV_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  -H 'Origin: https://yourcompany.com' \
  -H 'Content-Type: application/json' \
  -d '{ "email": "member@example.com", "password": "..." }'
```

### c) Refresh an expired token

```bash
curl -X POST 'https://{environment}/api/samlauthentication/partner/token' \
  -H 'X-Partner-Api-Key: gsk_sV_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  -H 'Origin: https://yourcompany.com' \
  -H 'Content-Type: application/json' \
  -d '{ "refreshToken": "<your-refresh-token>" }'
```

The previous refresh token is **revoked immediately** on every refresh — persist the new
one returned.

Member passwords are encrypted in transit. Please encrypt the password in the following way:

### Password transit encryption

Member passwords must be **AES-256-CBC encrypted** before being included in the request body. The server decrypts the password server-side before verifying it against the member's stored credentials. **Sending a plaintext password always returns&#x20;**`401 Unauthorized`**.**

#### Encryption algorithm

| Step           | Detail                                                                                                                                    |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| **Key**        | `SHA-256( UTF-8(hostname) )` — 32 bytes. `hostname` is the bare hostname of the URL you are POSTing to (e.g. `host-a.gladstonego.cloud`). |
| **IV**         | 16 cryptographically random bytes, generated fresh for every request.                                                                     |
| **Cipher**     | AES-256-CBC, PKCS7 padding.                                                                                                               |
| **Wire value** | `Base64( IV_bytes + ciphertext_bytes )` — the 16 IV bytes are prepended to the ciphertext before Base64 encoding.                         |

> **Hostname binding — important.** The encryption key is derived from the exact hostname you are calling. A password encrypted for `host-a.example.com` **will always fail** on `host-b.example.com`, even if both resolve to the same site. If your integration calls more than one environment URL, you must encrypt the password separately for each.

#### JavaScript (example)

```javascript
async function encryptPassword(plaintext, hostname) {
  const enc = new TextEncoder();

  // Derive 32-byte AES key from the request hostname
  const keyBytes = await crypto.subtle.digest('SHA-256', enc.encode(hostname));
  const key = await crypto.subtle.importKey(
    'raw', keyBytes, { name: 'AES-CBC' }, false, ['encrypt']
  );

  // Random 16-byte IV — must be unique per call
  const iv = crypto.getRandomValues(new Uint8Array(16));

  const ciphertext = await crypto.subtle.encrypt(
    { name: 'AES-CBC', iv },
    key,
    enc.encode(plaintext)
  );

  // Prepend IV to ciphertext and Base64-encode
  const combined = new Uint8Array(16 + ciphertext.byteLength);
  combined.set(iv, 0);
  combined.set(new Uint8Array(ciphertext), 16);

  return btoa(String.fromCharCode(...combined));
}

// Usage — hostname must match the URL you POST to
const hostname = 'host-a.gladstonego.cloud';
const encryptedPassword = await encryptPassword('memberP@ssword', hostname);
// Pass encryptedPassword as "password" in the request body
```

### Response

All three grants return the same JSON shape (illustrative — `expiresIn` is environment-dependent):

```json
{
  "accessToken": "eyJhbGciOi...",
  "refreshToken": "f3a1c4b2-...",
  "tokenType": "Bearer",
  "expiresIn": 3600
}
```

* `accessToken` — JWT. Send as `Authorization: Bearer <access-token>` on subsequent API calls.
* `refreshToken` — long-lived. Exchange via grant **(c)** before the access token expires.
* `tokenType` — always `"Bearer"`.
* `expiresIn` — seconds until `accessToken` expires. Treat this as the source of truth —
  the lifetime is configured per environment, so don't hardcode an assumed duration.

## Step 2 — Call a Gladstone API

After authentication, every API request must include:

```bash
curl 'https://{environment}/api/bookings/availability/...' \
-H 'Authorization: Bearer <accessToken from step 1>'
-H 'X-Use-SSO: true'
```

The bearer token carries your integration identity, tenant context, and the features
available to your account. It is accepted across the Gladstone API, giving you
access to the API endpoints relevant to your use case — for example bookings,
member profile, and activities.

Remember to pass the "X-Use-SSO" as a header to allow it to authenticate without user interaction.

### What your token can reach

You do not request scopes. The token request has no `scope` field, and every token issued from
an API key carries the same partner access. (The `cloud api` scope belongs to the legacy
[OAuth2 flow](oauth2-authentication.md) only.) What you can call is decided by three things:

1. **The endpoint must be open to partners.** Every endpoint in this portal's
   [API reference](../reference/getting-started.md) is. An endpoint that is not documented here refuses partner
   tokens with `403 Forbidden`.
2. **Your token must carry one of the endpoint's products and one of its features.** Each
   endpoint page lists them at the top, for example *Products: Operator (`FOH`), Consumer
   (`CONSUMER`) (any one of these products, not all)*. Any one listed value is enough; "not
   restricted" means that check does not apply. Which products and features your token carries
   depends on the token:
   - an **API-only token** carries the ones Gladstone configured for your integration;
   - a **member-scoped token** carries the ones the Gladstone customer you are working with is
     licensed for.
   A token without a match gets `403 Forbidden`.
3. **Your integration must be allowed the kind of call.** Gladstone sets read, write and delete
   permission for your integration: read covers `GET`, write covers `POST`, `PUT` and `PATCH`,
   and delete covers `DELETE`. A call your integration is not allowed returns `403 Forbidden`
   with a message such as `Partner token lacks can_write capability.`

Products, features and permissions are set on your **integration**, not on an individual key,
so every key you hold has the same access. They are written into the token when it is issued:
after Gladstone changes them, request a new token to pick the change up. To get access to an
endpoint you cannot reach, contact Gladstone with the endpoint and the products and features its
page lists.

## Rate limits

Every API key has per-minute and per-hour request limits configured by Gladstone for your
integration. If you exceed either limit you'll receive:

```http
HTTP 429 Too Many Requests
Retry-After: <seconds>
```

Wait the number of seconds indicated in `Retry-After` before retrying. Your integration
**must** handle 429 responses.

## Deep Link

To deep link into Go with a token, please navigate to Go using the following URL

```text
// Authenticated member, standard flow
https://[portal-domain]?SsoConfirmationToken=<token>

// Authenticated member, My Account
https://[portal-domain]/account?SsoConfirmationToken=<token>
```

## Errors

| Status | Means        | Likely cause                                                                                                                                                |
| ------ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | Bad request  | Malformed JSON or wrong grant-type body                                                                                                                     |
| 401    | Unauthorized | Missing, invalid, expired or revoked token or API key; unregistered Origin (browser); bad credentials; expired refresh token; password not encrypted |
| 403    | Forbidden    | Valid token, but no access to this endpoint: the endpoint is not open to partners, your token has none of its products or features, or your integration lacks read, write or delete permission. See [What your token can reach](#what-your-token-can-reach) |
| 429    | Rate-limited | Slow down; honour `Retry-After`                                                                                                                             |
| 500    | Server error | Transient — retry with backoff; contact us if it persists                                                                                                   |

## Security notes

* **Never put the API key in browser JavaScript** or any client-side code. Anyone viewing
  page source can copy it. Store it server-side only.
* **Browser integrations must use a registered domain.** Your domain has to be registered
  with Gladstone at setup time, and every request must include an `Origin` header matching
  it. Requests from unregistered domains are rejected with `401 Unauthorized`.
* **The Origin check only blocks another website's browser code from reusing the key** — it
  does not stop someone calling our API directly from a script, curl, or their own server.
  A key visible in page source is effectively public.
* **API keys can be revoked or rotated at any time.** Your integration must handle `401`
  auth failures gracefully.
* **Audit logging.** All authentication activity is logged against your API key — successful
  logins, failed attempts, token refreshes, and rate-limited requests. Gladstone
  administrators can review this log to support incident investigation and usage monitoring.
* **If a key is exposed, contact us to revoke and reissue** — we can do this without
  changing your partner record.

## Worked example — back-end proxy pattern

```text
[Browser]  --login form-->  [Your backend]
                                  |
                                  |--POST /partner/token (key + member creds)
                                  |   X-Partner-Api-Key, Origin
                                  v
                             [Gladstone]  -->  member-scoped JWT
                                  |
                                  v
                            [Browser]  <-- JWT
[Browser]  --bookings/availability JWT-->  [Gladstone]
```

This keeps the API key off the client entirely; the browser only ever carries a short-lived
member JWT.

## Migration from cookie-based authentication

If your existing integration relies on cookies, the table below summarises the required
changes:

| Currently            | Required change                                     |
| -------------------- | --------------------------------------------------- |
| Cookie-based session | Token-based auth (bearer JWT)                       |
| Implicit login state | Explicit token storage and refresh                  |
| Domain-dependent     | Works cross-domain (no Gladstone-domain dependency) |

## Important constraints

* API keys can be revoked at any time → integration must handle `401` failures.
* Access tokens have a finite lifetime (returned as `expiresIn`, in seconds) → refresh logic is **required**.
* API keys must not be exposed in browser or client code.
* Browser integrations must use approved, registered origins.
* Rate limits are enforced per API key → implement `429` + `Retry-After` handling.

## Quick-start checklist

To integrate with Gladstone APIs:

1. Obtain an API key from the Operator portal.
2. Register your domain (browser-based integrations only).
3. Call `POST /partner/token` to get a bearer token — the body determines the grant type
   (empty for API-only, `email`+`password` for member auth, `refreshToken` for refresh).
4. Send `Authorization: Bearer <access-token>` on every subsequent request.
5. Implement token refresh (on `401`) and rate-limit handling (on `429`).
6. Store and use tokens via your backend — never expose the API key in client code.
