---
title: API keys and rate limits
description: Why keyed requests price better, how limits are applied, and how to get a key.
---
import {
PANEL_URL,
RATE_LIMIT_KEYED_POINTS,
RATE_LIMIT_KEYLESS_POINTS,
KEYLESS_SPREAD_BPS,
} from "/snippets/vars.mdx";
Requests to `HularApi` may carry an `x-api-key` header. Nothing requires one, but running without a key costs you twice: tighter limits, and worse prices.
```ts
const client = createHularClient({
apiKey: process.env.HULAR_API_KEY,
});
```
```bash
grpcurl -H "x-api-key: $HULAR_API_KEY" ...
```
## Keyed versus keyless
| | With a key | Without |
| --- | --- | --- |
| Rate limit | {RATE_LIMIT_KEYED_POINTS} points per minute, raisable per key | {RATE_LIMIT_KEYLESS_POINTS} points per minute per client IP |
| Quote spread | Standard | Standard plus {KEYLESS_SPREAD_BPS} basis points |
| Attribution | Every quote records the key that requested it | Recorded against the client IP |
The keyless surcharge is folded into the spread component of the fee breakdown and is not itemized separately, so a keyless integration quietly quotes worse than a keyed one. If you are comparing Hular pricing against another route, compare with a key.
Sponsored gasless quotes (`sponsor_fees`) also require a key: the relay gas is charged to the funding balance of the partner the key belongs to. See [SubmitGaslessOrder](/api-reference/gasless).
## Getting a key
Keys are self-served from the partner panel at {PANEL_URL}. Sign in with an email code, create a key, and copy it once.
Request a code by email, then verify it. No password.
Name it after the surface that uses it. Each partner has a cap on active keys.
Send it as `x-api-key` on every `HularApi` request.
The same flow is available programmatically through `PartnerApi`; see [Partner panel](/integration/partner-panel).
## Request costs
Limits are measured in points, not requests. Every RPC has a fixed cost, declared as an option on the method in `hular/v1/api.proto`, and each request spends its cost from your per-minute budget. Expensive calls cost more, so one budget covers a mix of heavy quoting and cheap polling without penalizing either.
| Method | Cost |
| --- | --- |
| `GetQuote` | 50 |
| `PreviewQuote` | 30 |
| `SubmitGaslessOrder` | 30 |
| `GetSwapInstructions` | 25 |
| `GetTokenInfo` | 15 |
| `Search`, `SearchAddress` | 10 |
| `ListTokens`, `GetOrder`, `ListOrders`, `ListRecentOrders`, `GetChains` | 5 |
`PartnerApi` methods have their own costs in the same registry; they are cheap except for sign-in and voucher operations.
## Limits in practice
The budget is a token bucket: it holds at most your full per-minute quota and refills continuously at that rate, so short bursts up to the full quota are fine and sustained traffic settles at the refill rate. The bucket is shared across all API instances, and counted per key, so all your surfaces sharing one key share its budget. Use separate keys per application if you want them isolated from each other.
Exceeding the budget returns `RESOURCE_EXHAUSTED` with the message `rate limit exceeded`. Back off and retry; the bucket refills continuously rather than resetting on a window boundary.
The Usage tab in the partner panel charts your request volume, statuses, and points consumed per key; see [Partner panel](/integration/partner-panel).
Do not put a key in a browser bundle you do not control. It is attributable to you and rate limited as one budget. For public frontends, proxy quote requests through your backend.
## Key states
| Condition | Response |
| --- | --- |
| Unknown key | `UNAUTHENTICATED`, `unknown api key` |
| Revoked or disabled key | `UNAUTHENTICATED`, `api key disabled` |
| Over the limit | `RESOURCE_EXHAUSTED`, `rate limit exceeded` |
A missing header is not an error. The request is served as keyless.
Key changes propagate within a few seconds: keys are cached in memory and refreshed on a short interval, so a key created or revoked in the panel is not effective instantly.