How to Use API Key Profiles for Model Access Control and Restrictions in AxonHub
AxonHub implements fine-grained, per-API-key permissioning through API Key Profiles that whitelist models, map model names, filter provider channels, and enforce usage quotas without requiring application code changes.
AxonHub is an open-source LLM gateway that routes requests to multiple AI providers. The repository provides a sophisticated API Key Profile system that lets administrators control exactly which models each client can access, how requests are routed, and what usage limits apply. This article explains how to configure and use these profiles based on the actual implementation in looplj/axonhub.
Understanding the API Key Profile Data Model
API Key Profiles are defined in internal/objects/apikey.go. Each API key can have multiple profiles, but only one active profile is applied per request.
// internal/objects/apikey.go
type APIKeyProfiles struct {
ActiveProfile string `json:"activeProfile"` // name of the currently-active profile
Profiles []APIKeyProfile `json:"profiles"` // all defined profiles
}
type APIKeyProfile struct {
Name string `json:"name"` // unique profile name
ModelMappings []ModelMapping `json:"modelMappings"` // optional model remapping rules
ChannelIDs []int `json:"channelIDs,omitempty"`// allowed channel IDs
ChannelTags []string `json:"channelTags,omitempty"`// allowed channel tags
ModelIDs []string `json:"modelIDs,omitempty"` // whitelist of models
Quota *APIKeyQuota `json:"quota,omitempty"` // optional quota limits
LoadBalanceStrategy *string `json:"loadBalanceStrategy,omitempty"`
}
The orchestrator retrieves the active profile at request time via GetActiveProfile() in internal/server/orchestrator/state.go. If ActiveProfile is empty or the named profile does not exist, no restrictions are applied.
Configuring Model Access Control
The primary use case for API Key Profiles is model whitelisting. When a client sends a request, the orchestrator validates the requested model against the profile's ModelIDs list.
In internal/server/orchestrator/model_access.go, the enforcement logic checks:
// internal/server/orchestrator/model_access.go
profile := inbound.state.APIKey.GetActiveProfile()
if profile != nil && len(profile.ModelIDs) > 0 {
allowed := slices.Contains(profile.ModelIDs, llmRequest.Model)
if !allowed {
// request rejected with biz.ErrInvalidModel
return nil, fmt.Errorf("%w: %s", biz.ErrInvalidModel, llmRequest.Model)
}
}
Key behaviors:
- An empty
ModelIDsslice imposes no restrictions (all models allowed). - If the requested model is not in the list, AxonHub returns
biz.ErrInvalidModeland rejects the request immediately.
Advanced Profile Features
Beyond simple whitelisting, profiles support model name mapping, channel filtering, and quota enforcement.
Model Name Mapping
Profiles can rewrite client-requested model names to internal model identifiers using regex patterns. This is implemented in internal/server/orchestrator/model_mapper.go:
// internal/server/orchestrator/model_mapper.go (excerpt)
for _, mapping := range profile.ModelMappings {
if regexp.MatchString(mapping.RequestModel, llmRequest.Model) {
llmRequest.Model = mapping.TargetModel
break
}
}
This allows clients to use generic names like "gpt-4" while AxonHub routes to specific provider variants like "gpt-4-0613" or regional deployments.
Channel Filtering
Restrict which provider channels a request may use via ChannelIDs or ChannelTags. The selection logic in internal/server/orchestrator/select_candidates.go filters candidates:
// internal/server/orchestrator/select_candidates.go
if profile := inbound.state.APIKey.GetActiveProfile(); profile != nil {
if len(profile.ChannelIDs) > 0 {
selector = WithSelectedChannelsSelector(selector, profile.ChannelIDs)
}
if len(profile.ChannelTags) > 0 {
selector = WithTagsFilterSelector(selector, profile.ChannelTags)
}
}
Only channels matching the specified IDs or tags survive the selection step, ensuring requests route only to approved providers or regions.
Quota Enforcement
Profiles can define usage limits checked by internal/server/orchestrator/quota.go:
// internal/server/orchestrator/quota.go (excerpt)
profile := apiKey.GetActiveProfile()
if profile != nil && profile.Quota != nil {
result, err := quotaService.CheckAPIKeyQuota(ctx, apiKey.ID, profile.Quota)
// error → request rejected
}
Quota limits include request count, total tokens, cost, and time period definitions.
Practical Implementation
Configuring an API Key with Profiles
When creating an API key via the admin UI or direct database insertion, embed a JSON payload defining the profiles:
{
"name": "production-key",
"profiles": {
"activeProfile": "restricted-gpt4",
"profiles": [
{
"name": "restricted-gpt4",
"modelIDs": ["gpt-4", "gpt-4-32k"],
"channelTags": ["openai"],
"quota": {
"requests": 1000,
"totalTokens": 500000,
"period": {
"type": "calendar_duration",
"calendarDuration": { "unit": "day" }
}
}
},
{
"name": "admin-full-access",
"modelIDs": [],
"channelTags": []
}
]
}
}
Set profiles.activeProfile to the desired profile name. When a client calls the AxonHub OpenAI-compatible endpoint with this key, the orchestrator automatically applies the active profile's restrictions.
Go SDK Example
package main
import (
"context"
"log"
"github.com/looplj/axonhub/internal/objects"
"github.com/looplj/axonhub/internal/server/biz"
)
func main() {
ctx := context.Background()
// Build the profile payload
profiles := objects.APIKeyProfiles{
ActiveProfile: "restricted-gpt4",
Profiles: []objects.APIKeyProfile{
{
Name: "restricted-gpt4",
ModelIDs: []string{"gpt-4", "gpt-4-32k"},
ChannelTags: []string{"openai"},
Quota: &objects.APIKeyQuota{
Requests: ptrInt64(1000),
TotalTokens: ptrInt64(500_000),
Period: objects.APIKeyQuotaPeriod{
Type: objects.APIKeyQuotaPeriodTypeCalendarDuration,
CalendarDuration: &objects.APIKeyQuotaCalendarDuration{
Unit: objects.APIKeyQuotaCalendarDurationUnitDay,
},
},
},
},
},
}
// Use the biz service to persist the key
key := biz.NewAPIKey("production-key", profiles)
if err := biz.SaveAPIKey(ctx, key); err != nil {
log.Fatalf("save key failed: %v", err)
}
}
func ptrInt64(v int64) *int64 { return &v }
Testing the Restrictions
curl -X POST http://localhost:8090/v1/chat/completions \
-H "Authorization: Bearer <production-key>" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4",
"messages": [{"role":"user","content":"Hello"}]
}'
If the model were "gpt-3.5-turbo", AxonHub would reject the request with biz.ErrInvalidModel because the active profile only whitelists gpt-4 and gpt-4-32k.
Summary
- API Key Profiles in AxonHub provide fine-grained access control through the
APIKeyProfilestruct defined ininternal/objects/apikey.go. - Model whitelisting is enforced in
internal/server/orchestrator/model_access.goby checking theModelIDsslice against the requested model. - Model mapping allows transparent rewriting of model names via regex patterns in
internal/server/orchestrator/model_mapper.go. - Channel restrictions limit routing to specific provider channels using
ChannelIDsandChannelTagsininternal/server/orchestrator/select_candidates.go. - Quota enforcement tracks usage against limits defined in the profile via
internal/server/orchestrator/quota.go. - Profiles are activated by setting the
activeProfilefield in theAPIKeyProfilesJSON configuration.
Frequently Asked Questions
How do I restrict an API key to only specific models?
Set the modelIDs field in the API key profile to a list of allowed model identifiers. When modelIDs is non-empty, AxonHub validates every request against this whitelist in internal/server/orchestrator/model_access.go, rejecting any model not explicitly listed. An empty modelIDs slice allows all models.
Can I map client-facing model names to different internal models?
Yes. Use the modelMappings array in the profile to define regex patterns that match client-requested models and rewrite them to target internal model names. The orchestrator applies these mappings in internal/server/orchestrator/model_mapper.go before routing the request to provider channels.
What happens if a request exceeds the quota defined in the profile?
The quota middleware in internal/server/orchestrator/quota.go checks the current usage against the profile's quota limits before processing the request. If the request would exceed the defined limits for requests, tokens, or cost within the specified period, AxonHub rejects the request with a quota-exceeded error.
How do I switch between different profiles for the same API key?
Update the activeProfile field in the APIKeyProfiles struct to the name of the desired profile. The orchestrator calls GetActiveProfile() in internal/server/orchestrator/state.go at the start of each request to determine which profile rules to apply. You can change this field dynamically via the admin UI or API to instantly switch restrictions without rotating the key itself.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →