# Account security and alerts

> Protect your account with two-factor sign-in, lock API keys to IP addresses, cap their spend, and get an email or webhook alert before your balance runs out.

Source: https://oneaiapikey.com/docs/account-security

Your Relaykit account holds prepaid money, and your API keys can spend it. This page covers the settings that protect both: how you sign in, what each key may do, and how to hear about a low balance before requests start failing. Account settings are on the [Settings](https://oneaiapikey.com/console/settings) page of the console; key settings are on the [API keys](https://oneaiapikey.com/console/keys) page.

## Signing in

### Two-factor authentication

With two-factor authentication on, signing in needs your password and a six-digit code from an authenticator app such as 1Password, Bitwarden, Google Authenticator or Aegis. Sensitive changes, such as turning two-factor off or deleting the account, ask for a code too.

1. On the [Settings](https://oneaiapikey.com/console/settings) page, choose **Set up two-factor**. Confirm your password if asked. Setup must be finished within 5 minutes.
2. Add the account to your authenticator app with the setup key the console shows. It is a standard time-based key that makes six-digit codes.
3. Save the backup codes shown during setup somewhere safe, away from your phone. Each one works once in place of an app code.
4. Enter the current code from the app as the **6-digit code** and choose **Turn on**.

To turn two-factor off (**Turn off**) or get new backup codes (**New backup codes**), confirm with a code from the app or a backup code in the **Authentication code** field. New backup codes replace all the old ones. After several wrong codes in a row, code checks pause for a while before you can try again.

If you lose your phone, sign in with a backup code and set up the app again. If you have lost both, write to [support@relaykit.dev](mailto:support@relaykit.dev) from the account’s email address.

### Signed-in devices

The Settings page lists the browsers signed in to your account. End any session you do not recognise, or sign out everywhere else at once. Changing your password also signs out every other device. Your API keys keep working in both cases; they are separate from sign-in sessions.

## Protecting API keys

Each key can carry its own limits. Set them when you create the key, or later by editing it on the [API keys](https://oneaiapikey.com/console/keys) page.

| Setting | What it does | When a request breaks it |
| --- | --- | --- |
| **IP allowlist** | Only requests from the listed addresses are accepted. Set it in the **Allowed IP addresses** field when you create or edit a key. One IP address or CIDR range per line, for example `203.0.113.7` or `203.0.113.0/24`. IPv6 works too. Empty means any address. | `403` with “Your IP address is not on this key’s allowed list” |
| **Spend limit** | The key stops after spending this many dollars. | `403` while the rest is too small for one request, then `401` once used up |
| **Expiry date** | The key stops at the end of that day. | `401` |
| **Model list** | The key can only call the models you pick. | `403` with “This token has no access to model …” |

[Authentication and keys](https://oneaiapikey.com/docs/authentication#per-key-settings) explains spend limits, expiry dates and model lists in detail. The allowlist checks the address the request comes from as the gateway sees it: for a server behind NAT or a cloud load balancer, that is its public outgoing address, not its private one.

The **Last used** column on the keys page shows when each key was last used for a billed request, which makes forgotten keys easy to spot. A key that only calls free models may show as never used. Disable or revoke keys you no longer need.

> **A good default for production keys:** One key per app or server, an IP allowlist with that server’s address, a spend limit you could afford to lose, and only the models the app calls.

## Low-balance alerts

A low-balance alert tells you when your balance falls below a level you choose, so you can top up before requests start failing with `403`. On the [Settings](https://oneaiapikey.com/console/settings) page, under **Low-balance alerts**, choose **Email** or **Webhook** under **Send alerts by**, enter the level in **Alert when the balance is below (USD)** (for a webhook also the **Webhook URL** and, optionally, a **Signing secret**), then choose **Save alerts**.

How alerts behave:

- The gateway checks the balance after every billed request. If the balance left after that request is below your level, it sends an alert.
- While the balance stays below the level, alerts repeat, at most two in any ten minutes.
- If you set no level, the service’s default level applies.

### By webhook

The gateway sends a `POST` request with a JSON body to your URL. Any `2xx` response counts as delivered. Use a public HTTPS URL: the gateway does not send alerts to `localhost` or to private-network addresses.

| Field | Contents |
| --- | --- |
| `type` | `quota_exceed` for a low-balance alert. |
| `title` | A short heading. |
| `content` | The message text. |
| `values` | The parts filled into `content`: the heading, your balance as text and a link for adding credit. |
| `timestamp` | When the alert was sent, in Unix seconds. |

Base your handling on `type` and write your own message. The wording of `title` and `content` may change and is not guaranteed to be in English.

If you set a secret, each request carries an `X-Webhook-Signature` header: the hex-encoded HMAC-SHA256 of the raw request body, keyed with your secret. Check it before you act on the alert:

Python:

```python
import hashlib, hmac

def is_valid(raw_body: bytes, signature: str | None, secret: str) -> bool:
    """raw_body: the request body exactly as received, before any JSON parsing."""
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")

# signature = request.headers.get("X-Webhook-Signature")
```

Node:

```js
import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the request body exactly as received (a Buffer), before JSON.parse
export function isValid(rawBody, signature, secret) {
  const expected = Buffer.from(createHmac("sha256", secret).update(rawBody).digest("hex"));
  const given = Buffer.from(signature ?? "");
  return expected.length === given.length && timingSafeEqual(expected, given);
}

// const signature = req.headers["x-webhook-signature"];
```

### By email

If your account has an email address, alerts go to it by default, even before you save anything: then they start once the balance is nearly used up. Save a level to be warned earlier. Each email states your balance and links to **Billing**.

Alerts go only to the address on your account, never to another one. Accounts without an email address get alerts by webhook.

## If a key leaks

1. On the [API keys](https://oneaiapikey.com/console/keys) page, disable the key straight away. It stops working at once.
2. Create a replacement with an IP allowlist and a spend limit, and deploy it.
3. On the [usage page](https://oneaiapikey.com/console/usage), filter by the old key to see what it was used for.
4. Revoke the old key.
5. Remove the key from wherever it leaked: repository history, logs, shared documents.

If you see usage you did not make, write to [support@relaykit.dev](mailto:support@relaykit.dev) with the key’s name and the time range.

## Related

- [Authentication and keys](https://oneaiapikey.com/docs/authentication): sending keys, rotating them, and every per-key setting.
- [Cost control](https://oneaiapikey.com/docs/cost-control): spend limits, cheaper models and caching.
- [Routing and privacy](https://oneaiapikey.com/docs/routing-privacy): what the gateway records about each request.
