API Keys
Manage your API keys for programmatic access to the platform.
API keys are user-owned: each key belongs to the user who created it. You only see and manage the keys you created; teammates in the same organization manage their own keys separately.
Key List
View all your API keys with:
| Column | Description |
|---|---|
| Name | Descriptive key name |
| Key Preview | Key identifier (sk-proj-xxxx...xxxx) |
| Type | Inference or Management |
| API Scopes | Enabled API scopes for the key |
| Created | Creation date |
| Expires | Expiration date (if set) |
| Last Used | Most recent usage |
| Usage Count | Number of times the key has been used |
| Status | Active or Expired |
List controls
The key list has controls to help you manage many keys:
- Type filter shows All, Inference, or Management keys.
- Column show / hide toggles which columns are visible, so you can focus on what you care about.
- Row density switches between comfortable and compact rows.
- Restriction badges: keys with constraints show badges (models / IPs / spend). Click a badge to reveal the exact allowlist or cap applied to that key, so you can audit restrictions without opening each key.
- Analytics: every row has an Analytics action that opens the full traffic picture for that one key. See Key Analytics.
Creating a Key
- Click Create key
- Enter a Name (e.g., "Production Backend")
- Choose a Key Type
- Select the key's API Scopes
- Optionally set an expiry (see Expiry options)
- Optionally expand Advanced settings to apply constraints (see Constraints)
- Confirm
- Copy the key immediately
The new key is revealed once, under the heading "Your New API Key": "This key won't be shown again. Please save it in a secure location."
The full API key is only shown once. Copy and store it securely before closing the dialog.
Expiry options
| Option | Effect |
|---|---|
| 30 days | Expires 30 days from creation |
| 90 days | Expires 90 days from creation |
| 1 year | Expires one year from creation |
| Custom date | "Pick any future date. The key stops working at 00:00 UTC on that day." |
| Never expires | No expiry; rotate it on your own schedule |
Scope defaults
If you leave the scope selection untouched, the key is created with the full default set for its key type. An empty scope list is rejected, since a key must keep at least one scope.
Key Types and API Scopes
Choose the narrowest type and API scope set that fits the integration.
Inference Keys
Inference keys are for model calls and OpenAI-compatible SDK usage.
| API Scope | Access |
|---|---|
inference:chat | Chat completions |
inference:completions | Text completions |
inference:embeddings | Embeddings |
inference:images | Image generation |
inference:audio | The whole audio family: transcription, translation, speech synthesis, and the realtime transcription WebSocket |
models:list | Model listing |
models:retrieve | Single model metadata |
jobs:stats_own | Read your own async job statistics |
jobs:read_own | Read your own async jobs |
jobs:cancel_own | Cancel your own async jobs |
Async jobs are submitted through inference endpoints by setting the background/async request option. Direct POST /api/v1/jobs and DELETE /api/v1/jobs/{job_id} are not public API-key operations.
Management Keys
Management keys are for public portal automation. They cannot call inference endpoints.
| API Scope | Access |
|---|---|
usage:read_current | Current usage |
usage:read_history | Usage history |
usage:read_summary | Usage summary |
usage:read_report | Usage report export data |
usage:read_breakdown | Per-user tenant usage breakdown (admin/owner only) |
billing:read_current | Wallet and commitment (admin/owner only), plus current plan (any user) |
billing:read_cycles | Retired name. Grants what billing:read_invoices grants |
billing:read_invoices | Billing statements and transactions (admin/owner only) |
billing:read_summary | Retired name. Grants what billing:read_current grants |
profile:read | Current profile |
rate_limits:read | Current rate limits |
budgets:read_own | Read your own spend limit and the PAYG spend cap |
budgets:update_own | Update or clear your own spend limit; update or clear the PAYG spend cap (admin/owner only) |
Public API keys do not create, update, or delete API keys. Use the portal session for key management.
Management Scope Details
| API Scope | What it returns |
|---|---|
usage:read_current | Current-month usage summary with request, token, model, duration, and cost totals. Defaults to the key owner's personal usage. |
usage:read_history | Individual usage events with optional date, model, endpoint, API key, limit, and offset filters. Every event carries the api_key_id that produced it. Defaults to the key owner's personal usage. |
usage:read_summary | Aggregated usage summary for a required start_date/end_date range. Defaults to the key owner's personal usage. |
usage:read_report | Time-series usage report for a required date range, grouped hourly, daily, weekly, or monthly. Also grants the per-key analytics endpoint. Defaults to the key owner's personal usage. |
usage:read_breakdown | Per-user tenant usage breakdown. This endpoint requires the key owner to be a tenant admin or owner, so the portal only offers this scope to those roles. |
billing:read_current | Your wallet balance and its credit lots, your commitment contract if you have one, and your current plan status for the open period. The wallet and commitment reads require the key owner to be a tenant admin or owner; the plan read is open to any user in the tenant. |
billing:read_cycles | Retired name, kept working. Billing cycles were withdrawn as a concept, so this scope now grants exactly what billing:read_invoices grants. A key that already carries billing:read_invoices gains nothing from it. |
billing:read_invoices | Issued invoice documents, the transactions recorded against one invoice, and the tenant's transaction feed. Includes invoice line items, totals, due date, paid date, and payment status. Requires the key owner to be a tenant admin or owner. |
billing:read_summary | Retired name, kept working. It now grants exactly what billing:read_current grants. |
profile:read | The key owner's current public profile. |
rate_limits:read | The current effective rate limits for the key owner/tenant. |
budgets:read_own | The key owner's personal daily spend limit, and the tenant's pay-as-you-go spend cap. |
budgets:update_own | Updates or clears the key owner's personal daily spend limit, and the tenant's pay-as-you-go spend cap. Changing the PAYG spend cap requires the key owner to be a tenant admin or owner; changing the personal limit does not. |
Constraints
You can lock down how an API key is allowed to be used at creation time, and update those constraints later. Advanced settings on the create form holds three controls.
| Constraint | What it does |
|---|---|
| Model allowlist | Restrict an inference key to selected active models. Requests for any other model are rejected with 403 Forbidden. Leaving every model unchecked grants access to every active model the user is otherwise allowed to use. |
| IP allowlist | Restrict requests to one or more source IP addresses (CIDR ranges supported). Requests from any other IP are rejected with 403 Forbidden. Empty list = no IP restriction. |
| Daily spend cap (USD) | "Requests stop when this key reaches its daily spend cap." Capped per UTC day; the pre-request check returns 429 Too Many Requests once the cap is hit, and the counter resets at the start of the next UTC day. |
A key's daily spend cap is separate from your personal daily limit and your organization's, and it stacks with both. See Cost Limits for how the layers resolve.
Constraints are enforced by the gateway. Setting a field to empty (or sending null via the API) clears that constraint.
Use scope constraints to limit blast radius if a key is leaked: a key scoped to a single model, a single egress IP, and a $10/day cap is much less dangerous than an unscoped one.
Update a constraint via API
curl -X PATCH https://api.bulutistan.ai/api/v1/api-keys/{key_id} \
-H "Authorization: Bearer YOUR_JWT_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model_allowlist": ["your-chat-model"],
"ip_allowlist": ["203.0.113.10/32"],
"spend_cap_usd": 10.00
}'
Send null for any field to clear it. Scope changes are recorded as api_key_scope_updated audit events. See the Security section of your profile for account security information.
Naming Best Practices
Use descriptive names that include:
- Environment (Production, Staging, Development)
- Application name
- Purpose
Examples:
Production - Main BackendStaging - TestingDevelopment - Local
Key Security
Storage
- Do: Use environment variables
- Do: Use secrets managers (AWS Secrets, Vault)
- Don't: Commit to version control
- Don't: Share in plaintext
Rotation
Rotate keys regularly:
- Create a new key
- Update your applications
- Verify new key works
- Delete the old key
Operator visibility
Your platform operator can list your organization's API key metadata and revoke a key. What they see is the key's name, its preview prefix, type, scopes, restrictions, and its creation and last-used timestamps.
The secret itself is never visible to them. Only a hash of it is stored, so there is no path, for an operator or anyone else, that returns a usable key after creation.
If a key stops working unexpectedly and it has not expired, ask your platform administrator whether it was revoked.
Key Actions
Copying the Key
The full key can only be copied from the success banner shown immediately after creation. Key rows do not have a copy action; the list shows the key preview (sk-proj-xxxx...xxxx) for identifying keys in logs and debugging.
Delete Key
- Click the trash icon
- Confirm deletion
- Key is immediately revoked
Deletion is immediate and irreversible. Any applications using this key will stop working.
Key Analytics
The Analytics action on a key row opens a page for that credential alone. A key's row tells you how many requests it has made; this page tells you what it has been doing.
It is the Usage page scoped to one key, and deliberately so — the same board, the same numbers, narrowed:
- the same eight headline tiles, each with a delta pill comparing the selected range against the immediately preceding period of equal length
- the same range pills and the same Custom range picker
- the same Focus / Board / Overlay trend layouts over the same four measures: Requests, Tokens, Input tokens, Output tokens
- the Token distribution and Performance & cache insight cards
- the Usage by Model table
- Recent Activity, capped at 10 rows, with a Status column
The Status column is derived from whether the request succeeded and, where it failed, from the platform error code it returned. It is not an HTTP status code, and a failure that carries no error code simply reads Failed.
Like Usage, this page reports volume only: no cost tile, no cost column, no per-model cost. Amounts live on Billing.
A few Usage controls have no counterpart here: there are no p50/p95 percentiles, no per-model filter and no export.
The latency, cache and throughput rows are computed from individual request records, which are only read for ranges up to 31 days. On a longer range those rows render as em dashes with a caption saying why, while every figure derived from totals stays populated. Shorten the range to get them back.
Daily, weekly and monthly points are cut on your local day boundaries, including across daylight-saving transitions, so a request made just before local midnight is charted on the day you made it. Hourly granularity stays on UTC hours — an hour is an hour. The Usage page behaves the same way.
Who can see which key
Analytics are always scoped to your own organization.
- Owners and admins can open the analytics of any key in the organization.
- Members can open only their own keys.
A key id that does not exist, or that belongs to another organization, returns an empty report — every figure zero — rather than an error. The screen cannot be used to discover whether a key id exists.
Key Limits
| Limit | Value |
|---|---|
| Max keys per user | Set by your organization's tenant quota (platform default: 10; unlimited if configured so) |
| Key name length | 2–255 characters |
| Key expiration | Optional; must be a future date. The "1 year" option is a UI preset, not an enforced maximum |
Expiry Warnings
Keys that expire within the next 7 days display a small warning badge next to the expiry date showing the actual remaining days, for example 1 day, 3 days, or 7 days. This gives you a clear signal to rotate a key before it stops working.
Keys expiring in more than 7 days show no badge, just the expiry date.
Expired Keys
Expired keys are:
- Shown with "Expired" status
- Unable to make API calls
- Kept in the list until you delete them manually
API Reference
Manage API keys programmatically with a JWT access token from login. Public API keys cannot manage API keys.
List Your API Keys
curl https://api.bulutistan.ai/api/v1/api-keys \
-H "Authorization: Bearer YOUR_JWT_ACCESS_TOKEN"
Response:
{
"data": [
{
"id": "uuid",
"name": "Production Backend",
"key_type": "inference",
"key_preview": "sk-proj-PEyx...b01a",
"scopes": ["inference:chat", "models:list"],
"created_at": "2024-01-15T10:00:00Z",
"expires_at": "2024-07-15T10:00:00Z",
"last_used_at": "2024-01-20T15:30:00Z",
"usage_count": 40,
"is_active": true
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"total_pages": 1,
"has_next": false,
"has_prev": false
}
}
Create API Key
curl -X POST https://api.bulutistan.ai/api/v1/api-keys \
-H "Authorization: Bearer YOUR_JWT_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Production Backend",
"key_type": "inference",
"scopes": ["inference:chat", "models:list"],
"expires_at": "2024-12-31T23:59:59Z"
}'
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Descriptive name (2–255 chars) |
key_type | string | No | inference or management; defaults to inference |
scopes | string[] | No | API scope list for the selected key type. Omit it to receive the full default set for that type; an empty list is rejected |
expires_at | datetime | No | ISO 8601 expiration date (must be in the future) |
Response (201 Created):
{
"id": "uuid",
"name": "Production Backend",
"key_type": "inference",
"key": "sk-proj-abc123xyz789...",
"scopes": ["inference:chat", "models:list"],
"created_at": "2024-01-15T10:00:00Z",
"expires_at": "2024-12-31T23:59:59Z"
}
The key field contains the full API key and is only returned once at creation time. Store it securely.
Delete API Key
curl -X DELETE https://api.bulutistan.ai/api/v1/api-keys/{key_id} \
-H "Authorization: Bearer YOUR_JWT_ACCESS_TOKEN"
Response: 204 No Content
Reading Analytics Programmatically
curl "https://api.bulutistan.ai/api/v1/usage/api-keys/{key_id}/analytics?granularity=daily&start_date=2026-07-01&end_date=2026-07-31" \
-H "Authorization: Bearer YOUR_JWT_ACCESS_TOKEN"
This endpoint is also reachable with an API key that carries usage:read_report. A key created from the legacy usage:read grouping already has it, so no key needs re-issuing.
Query Parameters
| Parameter | Values | Description |
|---|---|---|
granularity | hourly, daily, weekly | Bucket size for the time series. It also caps how wide the range may be: 7 days hourly, 90 days daily, 365 days weekly |
model_name | model identifier | Optional. Narrows the totals and the time series to one model. The per-model breakdown stays unfiltered, so it can still populate a model selector |
The response carries the resolved range, totals for it, previous_totals for the immediately preceding window of equal length, the time series, a per-model breakdown, and a performance block with p50/p95 latency, cache counters and tokens per second.
Two behaviours are worth coding against:
- The series is dense. Buckets with no traffic are returned as zeros rather than omitted, so you can plot the array as-is without filling gaps yourself.
performanceis optional. For a range longer than 31 days it reportsavailable: falsewith null fields; every other block is still populated.
The same authorization rules as the portal apply: the read is scoped to your organization, owners and admins may read any key in it, other roles only their own, and an unknown or out-of-organization key id answers with an all-zero report instead of a 404.
Python Example
import requests
from datetime import datetime, timedelta
jwt_token = "YOUR_JWT_ACCESS_TOKEN"
base_url = "https://api.bulutistan.ai"
headers = {"Authorization": f"Bearer {jwt_token}"}
# List keys
response = requests.get(f"{base_url}/api/v1/api-keys", headers=headers)
result = response.json()
keys = result["data"]
print(f"You have {result['pagination']['total']} API keys")
# Create key with 6-month expiration
expires = (datetime.utcnow() + timedelta(days=180)).isoformat() + "Z"
response = requests.post(
f"{base_url}/api/v1/api-keys",
headers={**headers, "Content-Type": "application/json"},
json={
"name": "New Production Key",
"key_type": "inference",
"scopes": ["inference:chat", "models:list"],
"expires_at": expires,
}
)
new_key = response.json()
print(f"New key created: {new_key['name']}")
print(f"Full key (save this!): {new_key['key']}")
# Delete key
key_id = keys[0]["id"]
requests.delete(f"{base_url}/api/v1/api-keys/{key_id}", headers=headers)
API Key Access Scope
Inference keys work with /v1/* OpenAI-compatible endpoints and the matching /api/v1/* direct HTTP endpoints. Management keys work only with the public management endpoints listed above. Organization, member, tenant-admin, admin portal, and API-key management endpoints require a JWT session.
Troubleshooting
Key Not Working
- Check if key is expired
- Verify key is copied correctly (no extra spaces)
- Ensure
Bearerprefix in Authorization header - Check rate limits
Can't Create Keys
- You may have reached the maximum key limit
- Your account role may not allow key creation
- Contact your organization admin