# How to Use API Key Profiles for Model Access Control and Restrictions in AxonHub

> Secure your AxonHub models with API Key Profiles Limit access, whitelist models, filter channels, and enforce quotas without code changes. Master fine-grained control.

- Repository: [Loop/axonhub](https://github.com/looplj/axonhub)
- Tags: how-to-guide
- Published: 2026-03-06

---

**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`](https://github.com/looplj/axonhub/blob/main/internal/objects/apikey.go). Each API key can have multiple profiles, but only one **active profile** is applied per request.

```go
// 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`](https://github.com/looplj/axonhub/blob/main/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`](https://github.com/looplj/axonhub/blob/main/internal/server/orchestrator/model_access.go), the enforcement logic checks:

```go
// 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`](https://github.com/looplj/axonhub/blob/main/internal/server/orchestrator/model_mapper.go):

```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`](https://github.com/looplj/axonhub/blob/main/internal/server/orchestrator/select_candidates.go) filters candidates:

```go
// 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`](https://github.com/looplj/axonhub/blob/main/internal/server/orchestrator/quota.go):

```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:

```json
{
  "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

```go
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

```bash
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 `APIKeyProfile` struct defined in [`internal/objects/apikey.go`](https://github.com/looplj/axonhub/blob/main/internal/objects/apikey.go).
- **Model whitelisting** is enforced in [`internal/server/orchestrator/model_access.go`](https://github.com/looplj/axonhub/blob/main/internal/server/orchestrator/model_access.go) by checking the `ModelIDs` slice against the requested model.
- **Model mapping** allows transparent rewriting of model names via regex patterns in [`internal/server/orchestrator/model_mapper.go`](https://github.com/looplj/axonhub/blob/main/internal/server/orchestrator/model_mapper.go).
- **Channel restrictions** limit routing to specific provider channels using `ChannelIDs` and `ChannelTags` in [`internal/server/orchestrator/select_candidates.go`](https://github.com/looplj/axonhub/blob/main/internal/server/orchestrator/select_candidates.go).
- **Quota enforcement** tracks usage against limits defined in the profile via [`internal/server/orchestrator/quota.go`](https://github.com/looplj/axonhub/blob/main/internal/server/orchestrator/quota.go).
- Profiles are activated by setting the `activeProfile` field in the `APIKeyProfiles` JSON 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`](https://github.com/looplj/axonhub/blob/main/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`](https://github.com/looplj/axonhub/blob/main/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`](https://github.com/looplj/axonhub/blob/main/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`](https://github.com/looplj/axonhub/blob/main/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.