# How Scoped API Keys with Capability-Level Grants and KB Restrictions Function in WeKnora

> Learn how WeKnora's scoped API keys with capability grants and KB restrictions secure machine-to-machine interactions by authorizing specific actions on designated datasets.

- Repository: [Tencent/WeKnora](https://github.com/tencent/WeKnora)
- Tags: how-to-guide
- Published: 2026-09-12

---

**WeKnora's `TenantAPIKey` model enforces machine-to-machine security by binding API keys to specific capabilities (such as `retrieve` or `ingest`) and optional knowledge-base allow-lists, ensuring that automated clients can only perform explicitly authorized actions on designated data sets.**

Scoped API keys with capability-level grants and knowledge-base restrictions provide the foundation for secure automation in WeKnora. The system stores all machine-to-machine credentials in the `TenantAPIKey` structure, which defines both what actions a key may perform and which knowledge bases it can access. This design allows platform administrators to issue least-privilege credentials that minimize the blast radius of compromised API keys.

## Core Data Structures

WeKnora persists API key metadata in [`internal/types/tenant_api_key.go`](https://github.com/Tencent/WeKnora/blob/main/internal/types/tenant_api_key.go), where three primary structures collaborate to define access boundaries.

### TenantAPIKey

The **TenantAPIKey** struct represents the persistent storage of an API key. It captures the scope type, capability set, and optional knowledge-base restrictions.

```go
type TenantAPIKey struct {
    ScopeType        string        // "tenant" or "platform"
    FullAccess       bool          // Bypasses granular checks if true
    KnowledgeBaseIDs StringArray   // Allow-list of KB IDs (empty = unrestricted)
    Capabilities     StringArray   // e.g., "retrieve", "chat", "ingest"
    // ... additional fields like encrypted secret, name, tenant ID
}

```

### APIKeyCapability

**APIKeyCapability** enumerates the additive grants a key can receive. Defined constants include `retrieve`, `chat`, `ingest`, and `manage_kbs`, allowing fine-grained control over read, write, and administrative operations.

### TenantAPIKeyScope

The **TenantAPIKeyScope** struct serves as the request-level projection used by middleware and RBAC checks. It mirrors the persistent fields but is constructed fresh for each HTTP request to ensure current policy enforcement.

## Scope and Capability Normalization

Before enforcement, WeKnora normalizes inputs to guarantee consistent evaluation. This occurs in [`internal/types/tenant_api_key.go`](https://github.com/Tencent/WeKnora/blob/main/internal/types/tenant_api_key.go) through dedicated normalization functions.

**Scope normalization** (`NormalizeAPIKeyScopeType`) coerces any input to `"tenant"` or `"platform"`. Platform keys are the only ones that can bypass tenant-level RBAC checks.

**Capability normalization** (`NormalizeAPIKeyCapability` and `NormalizeAPIKeyCapabilities`) deduplicates capability strings and drops unknown values. This prevents injection of invalid capabilities and ensures the stored scope contains only recognized grants.

## Capability Evaluation with HasCapability

During request processing, handlers verify permissions via the **HasCapability** method on `TenantAPIKeyScope`. This function checks if the normalized capability list contains the requested action.

```go
func (s TenantAPIKeyScope) HasCapability(c APIKeyCapability) bool {
    c = NormalizeAPIKeyCapability(c)
    if c == "" { return false }
    for _, item := range NormalizeAPIKeyCapabilities(s.Capabilities) {
        if item == string(c) { return true }
    }
    return false
}

```

If the capability is absent, the handler returns **403 Forbidden**, blocking the operation before it reaches business logic.

## Knowledge-Base Restrictions

Beyond capabilities, keys may be **knowledge-base-restricted** through the `KnowledgeBaseIDs` field. When this array is non-empty, the key is confined to a specific subset of knowledge bases.

Two helper methods govern this restriction:

- **AllowsKnowledgeBase(kbID)** – Returns true when the allow-list is empty (unrestricted) or when `kbID` appears in `KnowledgeBaseIDs`.
- **IsKnowledgeBaseRestricted()** – Returns true if the allow-list contains one or more entries.

These checks ensure that a key created for ingesting data into `kb-123` cannot accidentally—or maliciously—access `kb-456`.

## Enforcement in Request Handlers

HTTP middleware and route handlers rely on two authorization helpers defined in [`internal/types/tenant_api_key.go`](https://github.com/Tencent/WeKnora/blob/main/internal/types/tenant_api_key.go) to enforce KB restrictions at the edge.

### AuthorizeTenantAPIKeyKnowledgeBases

Invoked by any route touching one or more knowledge bases (e.g., ingest, retrieve, copy), this function verifies that every requested KB ID appears in the key's allow-list. If the key is KB-restricted and any requested ID is missing, it returns **403 Forbidden**.

### AuthorizeTenantAPIKeyKnowledgeTargets

Used by routes accepting explicit `knowledge_ids` (such as search or history), this helper rejects calls when callers supply knowledge identifiers without a verified KB or when any `kb_id` falls outside the allow-list.

Route definitions in [`router/routes_knowledge.go`](https://github.com/Tencent/WeKnora/blob/main/router/routes_knowledge.go) demonstrate typical usage:

```go
router.POST("/knowledge/:kb_id/ingest", func(c echo.Context) error {
    kbID := c.Param("kb_id")
    if err := types.AuthorizeTenantAPIKeyKnowledgeBases(c.Request().Context(), kbID); err != nil {
        return err // 403 if KB not allowed
    }
    scope, _ := types.TenantAPIKeyScopeFromContext(c.Request().Context())
    if !scope.HasCapability(types.APIKeyCapabilityIngest) {
        return errors.NewForbiddenError("API key lacks ingest capability")
    }
    // ...perform ingest logic...
})

```

## The Complete Authorization Flow

The enforcement of scoped API keys follows a five-step pipeline for every authenticated request:

1. **Authentication** – Middleware extracts the `X-API-Key` header, decrypts the stored secret, and builds a `TenantAPIKeyScope` instance.
2. **Scope Normalisation** – `ScopeType` is normalized; platform keys bypass tenant-level checks, while tenant keys continue through RBAC.
3. **Capability Evaluation** – Handlers call `HasCapability` to validate actions like *chat*, *ingest*, or *manage_kbs*.
4. **KB Restriction Verification** – If `KnowledgeBaseIDs` is non-empty, `AuthorizeTenantAPIKeyKnowledgeBases` confirms every touched KB is in the allow-list.
5. **Authorization Decision** – Any check failure triggers a **403 Forbidden** response; otherwise, the operation proceeds.

## Practical Implementation Examples

Creating and enforcing a restricted key requires coordination between storage, middleware, and handlers.

```go
// 1. Create a scoped API key (admin UI or CLI)
key := types.TenantAPIKey{
    TenantID:   ptrUint64(42),
    ScopeType:  types.APIKeyScopeTenant,
    Name:       "ingest-key-for-kb-123",
    FullAccess: false,
    KnowledgeBaseIDs: types.StringArray{"kb-123"},
    Capabilities:     types.StringArray{string(types.APIKeyCapabilityIngest)},
}
db.Create(&key) // APIKey is encrypted automatically

// 2. Middleware extracts scope for each request
func apiKeyMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        keyStr := c.Request().Header.Get("X-API-Key")
        // lookup → TenantAPIKey → TenantAPIKeyScope (omitted for brevity)
        scope := types.TenantAPIKeyScope{
            KeyID:            key.ID,
            ScopeType:        key.ScopeType,
            FullAccess:       key.FullAccess,
            KnowledgeBaseIDs: key.KnowledgeBaseIDs,
            Capabilities:     key.Capabilities,
        }
        ctx := types.WithTenantAPIKeyScope(c.Request().Context(), scope)
        c.SetRequest(c.Request().WithContext(ctx))
        return next(c)
    }
}

// 3. Handler allowing only "retrieve" on a specific KB
func getDocumentHandler(c echo.Context) error {
    kbID := c.Param("kb_id")
    if err := types.AuthorizeTenantAPIKeyKnowledgeBases(c.Request().Context(), kbID); err != nil {
        return err // 403 if KB not allowed
    }
    scope, _ := types.TenantAPIKeyScopeFromContext(c.Request().Context())
    if !scope.HasCapability(types.APIKeyCapabilityRetrieve) {
        return errors.NewForbiddenError("API key lacks retrieve capability")
    }
    // ...fetch and return document...
    return nil
}

```

## Summary

- **TenantAPIKey** stores persistent credentials with `ScopeType`, `Capabilities`, and optional `KnowledgeBaseIDs`.
- **Capability-level grants** use `HasCapability` to validate that a key holds the required permission for an action.
- **KB restrictions** are enforced via `AuthorizeTenantAPIKeyKnowledgeBases` and `AllowsKnowledgeBase`, creating hard boundaries around data access.
- **Normalization** functions guarantee consistent handling of scope types and capabilities during storage and runtime checks.
- **Platform keys** bypass tenant-level checks, while **tenant keys** are fully constrained by capability and knowledge-base allow-lists.

## Frequently Asked Questions

### What is the difference between tenant and platform scoped API keys in WeKnora?

Tenant-scoped keys operate within a single tenant's boundaries and must pass both capability and knowledge-base checks, while platform-scoped keys bypass tenant-level RBAC entirely. According to the source code in [`internal/types/tenant_api_key.go`](https://github.com/Tencent/WeKnora/blob/main/internal/types/tenant_api_key.go), platform keys are normalized to `"platform"` and are typically reserved for infrastructure-level automation.

### How does WeKnora validate that an API key has permission to access a specific knowledge base?

The system invokes `AuthorizeTenantAPIKeyKnowledgeBases` from [`internal/types/tenant_api_key.go`](https://github.com/Tencent/WeKnora/blob/main/internal/types/tenant_api_key.go), which checks `AllowsKnowledgeBase` for every requested KB ID. If the key's `KnowledgeBaseIDs` array is non-empty and does not contain the requested ID, the function returns a **403 Forbidden** error before the handler executes.

### What happens if an API key attempts to use a capability it does not possess?

When a handler calls `scope.HasCapability(capability)` and the capability string is absent from the key's normalized capability list, the method returns false. The handler then returns **403 Forbidden**, rejecting the request without touching business logic or data layers.

### How are API key capabilities normalized before storage and validation?

`NormalizeAPIKeyCapabilities` deduplicates the capability array and drops unknown strings, while `NormalizeAPIKeyCapability` validates individual capability values against the enumerated constants. This ensures that only recognized grants like `ingest` or `retrieve` are persisted and evaluated, preventing policy pollution from invalid input.