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 ModelIDs slice imposes no restrictions (all models allowed).
  • If the requested model is not in the list, AxonHub returns biz.ErrInvalidModel and 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

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →