API keys and authentication
How sk-up keys work — the Authorization and X-API-Key headers, per-key spending limits, model allow-lists, rate limits, expiry and instant revocation.
Updated: September 7, 2026
Every request to https://api.uttapen.ir/v1 needs an API key. A key belongs to your account, spends from that account's wallet, and can carry limits of its own. This page covers creating keys, sending them and managing them.
Key format
A key starts with sk-up- followed by 40 random characters. The sk- prefix is deliberate, so that tools which validate the shape of an OpenAI key (some editor extensions and unofficial SDKs) keep working. The full key is displayed exactly once, at creation time: we store only a hash of it, and from then on the dashboard shows you the prefix and the last four characters.
Sending the key
Two forms are accepted and they are equivalent:
Authorization: Bearer sk-up-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-API-Key: sk-up-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
The official OpenAI SDKs send the first form on their own — just pass api_key. X-API-Key exists for tools that already use the Authorization header for something else. If you send both, Authorization wins.
With no key, or an invalid one, you get 401 with the code invalid_api_key:
{"error":{"message":"The API key is invalid, revoked or expired.","type":"invalid_api_key","code":"invalid_api_key","param":null}}
The only routes that work without a key are GET /v1/models, GET /v1/models/{id} and GET /healthz.
Per-key settings
You can set all of these per key in Dashboard → Keys. They are all optional and can be changed after the key is created.
| Setting | Default | Behaviour |
|---|---|---|
name | — | For your own bookkeeping; returned by GET /v1/uttapen/me |
monthly_limit_toman | no limit | Total cost of this key's requests in the current Jalali month plus any open holds. Going over returns 429 key_monthly_limit_reached |
allowed_models | all models | The list of model ids this key may call. Anything else gets 403 model_not_allowed; the models fallback list is checked against the same rule |
rate_limit_rpm | 60 | Requests per minute, sliding window. Allowed range is 1 to 600 |
expires_at | never expires | After this moment the key returns 401 |
Put a monthly limit on your main production key, and for a key you hand to a script or a teammate narrow allowed_models down to one or two cheap models. That contains unexpected spend without needing a separate wallet.
Revoking a key
Revoking from the dashboard takes effect immediately: the gateway cache is cleared at once and the next request with that key gets 401, even a second after revocation. Streams that were already running finish normally and are billed as usual. A revoked key never comes back — create a new one.
Checking key and account state
GET /v1/uttapen/me, called with the key itself, returns the state of the key and the wallet. It is a handy connectivity check before you deploy.
curl https://api.uttapen.ir/v1/uttapen/me \
-H "Authorization: Bearer $UTTAPEN_API_KEY"
{
"user": {"id": "01900000-0000-7000-8000-000000000001", "phone_masked": "0912***0001"},
"key": {
"name": "local-dev",
"prefix": "sk-up-62nBtYlq",
"monthly_limit_toman": null,
"spent_this_month_toman": "2402.551503",
"rate_limit_rpm": 60,
"allowed_models": null
},
"wallet": {
"balance_toman": "97571.073497",
"held_toman": "0.000000",
"available_toman": "97571.073497"
},
"prices_updated_at": "2026-09-06T22:19:22Z"
}
All amounts are strings so that decimal precision survives the round trip. In JavaScript, don't pass them through Number — use decimal.js or render the string directly. available_toman is balance_toman minus held_toman, and it is the number that decides whether your next request is accepted. prices_updated_at is the last time model prices were refreshed.
Security notes
- Keep keys out of source code, repositories and clients (browsers, mobile apps). Read them from an environment variable or a secret manager. If one lands in a commit, revoke it immediately.
- For browser and mobile traffic, call uttapen from your own server. There is a proxy example in streaming.
- Create a separate, clearly named key per environment (development, staging, production) so that usage reports split cleanly with
group_by=key. - Set
expires_aton temporary keys. People forget to delete keys; servers don't. - The gateway doesn't store the content of API requests (privacy), but a leaked key can still drain your wallet. A monthly limit is the cheapest insurance you can buy.
Don't give us your own OpenAI or OpenRouter key, and don't send your uttapen key to another service. Each key only means something inside its own system.