---
title: API keys
description: Issue a separate Half Off Dev key for every app, cap its spend, rate and models, and rotate or revoke it without breaking production.
---

A Half Off Dev key is the only thing your code needs to reach Claude, GPT and Gemini at half the official price. Every key draws on the same prepaid balance, but each one carries its own limits, so a runaway script on one key can't drain what the rest of your stack depends on. Create as many as you want.

## Make a new key {#create-a-key}

:::steps
### Open the keys page {#open-the-keys-page}

In the dashboard, go to [API keys](/app/keys) and press **New key**.

### Give it a name and a group {#name-and-group}

Pick a name that tells you where the key lives, for example `support-bot-prod` or `nightly-evals`. **Channel group** can stay on **Auto — best available**; choose Claude · Value, GPT · Value or Gemini · Value only if every call from this key must run in that group. More in [Channel groups](/docs/models/channel-groups).

### Add limits if you want them {#add-limits}

**Limits & restrictions** lets you cap spend, request rate, concurrency, allowed models and lifetime. The table [below](#key-options) explains each setting. All of them except the group can be changed later.

### Copy it while you can {#copy-the-key}

Press **Create**. A banner at the top of the page shows the full key a single time. Copy it now, or press **Use in tools…** to configure Claude Code, Codex CLI and similar clients with it straight away. Once you press **Done**, the list only shows the start of the key, such as `ho-a1b2c3…`.
:::

> [!WARNING] You see the full key once
> Half Off Dev keeps a hash, not the key itself, so nobody can show it to you again. A lost key has to be revoked and replaced.

## Settings you can put on a key {#key-options}

| Setting | Default | Effect |
| --- | --- | --- |
| **Name** | — | Up to 60 characters. Appears in the key list and next to each request in [Logs](/app/logs). |
| **Channel group** | Auto | Pins every request from the key to one channel group. **Auto** chooses the best available group request by request. Fixed at creation. |
| **Spend limit (USD)** | No limit | The most this key can ever spend, in whole dollars. It never resets: think lifetime cap, not monthly budget. |
| **Expires on** | Never | The key is refused once this day is over, in your browser's time zone. |
| **Requests per minute** | Unlimited | How many model requests the key may start in any minute. |
| **Max concurrent requests** | Unlimited | How many requests the key may have open at the same moment. A stream counts until its last byte. |
| **Only allow specific models** | All models | The models this key may call. An alias such as `claude-sonnet-5-5` is checked as the model it points to. |

In the key list you can see each key's group and limits, how much it has **Spent**, and when it was **Last used**.

## When a key hits a limit {#limit-errors}

Half Off Dev refuses the request before it reaches a model. You are not charged, nothing is written to Logs, and the error comes back in the format of the endpoint you called.

| Cause | Status | Anthropic `type` | OpenAI `type` | Message |
| --- | --- | --- | --- | --- |
| Past the expiry date | `401` | `authentication_error` | `authentication_error` | `This API key has expired` |
| Revoked | `401` | `authentication_error` | `authentication_error` | `Invalid or revoked API key` |
| Model outside the allow-list | `403` | `permission_error` | `permission_denied` | `This API key is not allowed to use 'gpt-6.1-sol'.` |
| Spend limit used up | `402` | `billing_error` | `insufficient_quota` | `This API key has reached its spend limit.` |
| Too many requests per minute, or too many at once | `429` | `rate_limit_error` | `rate_limit_exceeded` | `This API key has reached its rate or concurrency limit.` |

Requests to the Gemini endpoints get the same status codes, written in Gemini's error shape. A `429` caused by a key limit includes a `Retry-After` header with the number of seconds to wait.

The spend-limit refusal, as each SDK receives it:

```json title="Anthropic"
{
  "type": "error",
  "error": {
    "type": "billing_error",
    "message": "This API key has reached its spend limit."
  }
}
```

```json title="OpenAI"
{
  "error": {
    "message": "This API key has reached its spend limit.",
    "type": "insufficient_quota",
    "code": "insufficient_quota"
  }
}
```

The spend limit is checked when a request starts. A request that is already running is allowed to finish, so a key's **Spent** can end up a little above its limit. Every other error body, and when to retry, is in [Errors](/docs/api/errors#status-codes).

## Change a key later {#edit-a-key}

Press **Edit** on a key to update its name, spend limit, expiry, rate and concurrency limits or model allow-list. The next request already sees the change. Empty a field to remove that limit.

The channel group is the one setting you can't edit. To move traffic to another group, create a key in that group and [rotate](#rotate-a-key) over to it.

## Swap keys without downtime {#rotate-a-key}

1. Create the replacement with the same group and limits.
2. Ship it to your app or secrets manager.
3. Watch the old key's **Last used** time until it stops moving.
4. Revoke the old key.

Do this on a regular schedule, whenever someone with access leaves the team, and right away if a key might have been exposed.

## Revoke a key {#revoke-a-key}

Press **Revoke**, then **Confirm revoke**. From the next request on, the key is rejected, and there is no undo. It stays in the list, greyed out, with its spend history, and its past requests remain in [Logs](/app/logs).

## Connect a coding client {#use-in-clients}

Every key has a **Use in tools** button with ready-made settings for Claude Code, Codex CLI, Gemini CLI, Cherry Studio, DeepChat and AionUI, including one-click import for apps that support it. Each client has its own guide under [Integrations](/docs/integrations).

## Habits that keep spend under control {#best-practices}

- **A key per app and per environment.** Production, staging, CI and your own laptop each get their own key, so you can read spend per key and revoke one without touching the rest.
- **Cap anything that runs without a human watching.** Batch jobs, CI pipelines and retry loops can spin out of control. A spend limit bounds the cost; a requests-per-minute limit slows the spin down.
- **Allow only what the job uses.** A key that classifies tickets with a small model has no reason to reach the largest one.
- **Put an end date on temporary keys.** Demos, contractors and trials get a key that expires by itself.
- **Never ship a key to a browser or phone.** Call Half Off Dev from your server. See [Authentication](/docs/authentication).
