> **Building with AI coding agents?** If you're using an AI coding agent, install the official Scalekit plugin. It gives your agent full awareness of the Scalekit API — reducing hallucinations and enabling faster, more accurate code generation.
>
> - **Claude Code**: `/plugin marketplace add scalekit-inc/claude-code-authstack` then `/plugin install <auth-type>@scalekit-auth-stack`
> - **GitHub Copilot CLI**: `copilot plugin marketplace add scalekit-inc/github-copilot-authstack` then `copilot plugin install <auth-type>@scalekit-auth-stack`
> - **Codex**: run the bash installer, restart, then open Plugin Directory and enable `<auth-type>`
> - **Skills CLI** (Windsurf, Cline, 40+ agents): `npx skills add scalekit-inc/skills --list` then `--skill <skill-name>`
>
> `<auth-type>` / `<skill-name>`: `agentkit`, `full-stack-auth`, `mcp-auth`, `modular-sso`, `modular-scim` — [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# API keys

Issue long-lived, revocable API keys scoped to organizations and users for programmatic access to your APIs
When your customers integrate with your APIs — whether for CI/CD pipelines, partner integrations, or internal tooling — they need a straightforward way to authenticate. Scalekit API keys give you long-lived, revocable bearer credentials for organization-level or user-level access to your APIs.

In this guide, you'll learn how to create, validate, list, and revoke API keys using the Scalekit.

```d2 pad=36
shape: sequence_diagram

Customer's App (API Client)
User
Your App
Scalekit

User -> Your App: Request API key
Your App -> Scalekit: Create token (organizationId, userId?)
Scalekit -> Your App: API key + tokenId
Your App -> User: API key
User -> Customer's App (API Client): Configure API key
Customer's App (API Client) -> Your App: Request with Authorization header
Your App -> Scalekit: Validate token
Scalekit -> Your App: Organization ID (+ User ID if user-scoped)
Your App -> Your App: Filter data by org and user context
Your App -> Customer's App (API Client): Response
```

> tip
>
> The plain-text API key is returned **only at creation time**. Scalekit does not store the key and cannot retrieve it later. Instruct your users to copy and store the key securely before closing the creation dialog.
>
> **Organization vs user-scoped keys**: The `userId` parameter is optional. If omitted, the key is organization-scoped and grants access to all resources in that workspace. If included, the key is user-scoped and your API uses the returned user context to filter data to only that user's resources.

1. ## Install the SDK

   ### Node.js

```bash showLineNumbers=false frame="none"
npm install @scalekit-sdk/node
```

   ### Python

```sh showLineNumbers=false frame="none"
pip install scalekit-sdk-python
```

  ### Go

```sh showLineNumbers=false frame="none"
go get -u github.com/scalekit-inc/scalekit-sdk-go
```

   ### Java

```groovy showLineNumbers=false frame="none"
/* Gradle users - add the following to your dependencies in build file */
implementation "com.scalekit:scalekit-sdk-java:2.0.11"
```

```xml showLineNumbers=false frame="none"
<!-- Maven users - add the following to your `pom.xml` -->
<dependency>
    <groupId>com.scalekit</groupId>
    <artifactId>scalekit-sdk-java</artifactId>
    <version>2.0.11</version>
</dependency>
```

   Initialize the Scalekit client with your environment credentials:

   
   ### Node.js

```javascript title="Express.js" collapse={1-2}
import { ScalekitClient } from '@scalekit-sdk/node';

const scalekit = new ScalekitClient(
  process.env.SCALEKIT_ENVIRONMENT_URL,
  process.env.SCALEKIT_CLIENT_ID,
  process.env.SCALEKIT_CLIENT_SECRET
);
```

   ### Python

```python title="Flask" collapse={1-2}
import os
from scalekit import ScalekitClient

scalekit_client = ScalekitClient(
    env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"],
    client_id=os.environ["SCALEKIT_CLIENT_ID"],
    client_secret=os.environ["SCALEKIT_CLIENT_SECRET"],
)
```

   ### Go

```go title="Gin" collapse={1-2}
import scalekit "github.com/scalekit-inc/scalekit-sdk-go/v2"

scalekitClient := scalekit.NewScalekitClient(
  os.Getenv("SCALEKIT_ENVIRONMENT_URL"),
  os.Getenv("SCALEKIT_CLIENT_ID"),
  os.Getenv("SCALEKIT_CLIENT_SECRET"),
)
```

   ### Java

```java title="Spring Boot" collapse={1-2}
import com.scalekit.ScalekitClient;

ScalekitClient scalekitClient = new ScalekitClient(
    System.getenv("SCALEKIT_ENVIRONMENT_URL"),
    System.getenv("SCALEKIT_CLIENT_ID"),
    System.getenv("SCALEKIT_CLIENT_SECRET")
);
```

   

2. ## Create a token

   To get started, create an API key scoped to an organization. You can optionally scope it to a specific user and attach custom metadata.

   ### Organization-scoped API key

   **When to use**: Organization-scoped keys are for customers who need full access to all resources within their workspace or account. When they authenticate with the key, Scalekit validates it and confirms the organization context — your API then exposes all resources they own.

   **Example scenario**: You're building a CRM like HubSpot. Your customer integrates with your API using an organization-scoped key. When they request contacts, tasks, or deals, the key validates successfully for their organization, and your API returns all resources in that workspace.

   This is the most common pattern for service-to-service integrations where the API key represents access on behalf of an entire organization.

   
   ### Node.js

```javascript
try {
  const response = await scalekit.token.createToken(organizationId, {
    description: 'CI/CD pipeline token',
  });

  // Store securely — this value cannot be retrieved again after creation
  const opaqueToken = response.token;
  // Stable identifier for management operations (format: apit_xxxxx)
  const tokenId = response.tokenId;
} catch (error) {
  console.error('Failed to create token:', error.message);
}
```

   ### Python

```python
try:
    response = scalekit_client.tokens.create_token(
        organization_id=organization_id,
        description="CI/CD pipeline token",
    )

    opaque_token = response.token  # store this securely
    token_id = response.token_id   # format: apit_xxxxx
except Exception as e:
    print(f"Failed to create token: {e}")
```

   ### Go

```go
response, err := scalekitClient.Token().CreateToken(
  ctx, organizationId, scalekit.CreateTokenOptions{
    Description: "CI/CD pipeline token",
  },
)
if err != nil {
  log.Printf("Failed to create token: %v", err)
  return
}

// Store securely — this value cannot be retrieved again after creation
opaqueToken := response.Token
// Stable identifier for management operations (format: apit_xxxxx)
tokenId := response.TokenId
```

   ### Java

```java
import com.scalekit.grpc.scalekit.v1.tokens.CreateTokenResponse;

try {
    CreateTokenResponse response = scalekitClient.tokens().create(organizationId);

    // Store securely — this value cannot be retrieved again after creation
    String opaqueToken = response.getToken();
    // Stable identifier for management operations (format: apit_xxxxx)
    String tokenId = response.getTokenId();
} catch (Exception e) {
    System.err.println("Failed to create token: " + e.getMessage());
}
```

   

   ### User-scoped API key

   **When to use**: User-scoped keys enable fine-grained data filtering based on who owns the key. Your API validates the key, receives the user context, and then exposes only data relevant to that user — enabling role-based filtering without additional database lookups.

   **Example scenario**: Your CRM has a `/tasks` endpoint. One customer gives their team member a user-scoped API key. When that person calls `/tasks`, the key validates for their organization _and_ user, and your API returns only tasks assigned to them — not all tasks in the workspace. Another team member with a different key sees only their own tasks.

   User-scoped keys enable personal access tokens, per-user audit trails, and user-level rate limiting. You can also attach custom claims as key-value metadata.

   
   ### Node.js

```javascript
try {
  const userToken = await scalekit.token.createToken(organizationId, {
    userId: 'usr_12345',
    customClaims: {
      team: 'engineering',
      environment: 'production',
    },
    description: 'Deployment service token',
  });

  const opaqueToken = userToken.token;
  const tokenId = userToken.tokenId;
} catch (error) {
  console.error('Failed to create token:', error.message);
}
```

   ### Python

```python
try:
    user_token = scalekit_client.tokens.create_token(
        organization_id=organization_id,
        user_id="usr_12345",
        custom_claims={
            "team": "engineering",
            "environment": "production",
        },
        description="Deployment service token",
    )

    opaque_token = user_token.token
    token_id = user_token.token_id
except Exception as e:
    print(f"Failed to create token: {e}")
```

   ### Go

```go
userToken, err := scalekitClient.Token().CreateToken(
  ctx, organizationId, scalekit.CreateTokenOptions{
    UserId:      "usr_12345",
    CustomClaims: map[string]string{
      "team":        "engineering",
      "environment": "production",
    },
    Description: "Deployment service token",
  },
)
if err != nil {
  log.Printf("Failed to create user token: %v", err)
  return
}

opaqueToken := userToken.Token
tokenId := userToken.TokenId
```

   ### Java

```java
import java.util.Map;
import com.scalekit.grpc.scalekit.v1.tokens.CreateTokenResponse;

try {
    Map customClaims = Map.of(
        "team", "engineering",
        "environment", "production"
    );

    CreateTokenResponse userToken = scalekitClient.tokens().create(
        organizationId, "usr_12345", customClaims, null, "Deployment service token"
    );

    String opaqueToken = userToken.getToken();
    String tokenId = userToken.getTokenId();
} catch (Exception e) {
    System.err.println("Failed to create token: " + e.getMessage());
}
```

   

   The response contains three fields:

   | Field | Description |
   |-------|-------------|
   | `token` | The API key string. **Returned only at creation.** |
   | `token_id` | An identifier (format: `apit_xxxxx`) for referencing the token in management operations. |
   | `token_info` | Metadata including organization, user, custom claims, and timestamps. |

3. ## Validate a token

   When your API server receives a request with an API key, you'll want to verify it's legitimate before processing the request. Pass the key to Scalekit — it validates the key server-side and returns the associated organization, user, and metadata context.

   
   ### Node.js

```javascript
import { ScalekitValidateTokenFailureException } from '@scalekit-sdk/node';

try {
  const result = await scalekit.token.validateToken(opaqueToken);

  const orgId = result.tokenInfo?.organizationId;
  const userId = result.tokenInfo?.userId;
  const claims = result.tokenInfo?.customClaims;
} catch (error) {
  if (error instanceof ScalekitValidateTokenFailureException) {
    // Token is invalid, expired, or revoked
    console.error('Token validation failed:', error.message);
  }
}
```

   ### Python

```python
from scalekit import ScalekitValidateTokenFailureException

try:
    result = scalekit_client.tokens.validate_token(token=opaque_token)

    org_id = result.token_info.organization_id
    user_id = result.token_info.user_id
    claims = result.token_info.custom_claims
except ScalekitValidateTokenFailureException:
    # Token is invalid, expired, or revoked
    print("Token validation failed")
```

   ### Go

```go
result, err := scalekitClient.Token().ValidateToken(ctx, opaqueToken)
if errors.Is(err, scalekit.ErrTokenValidationFailed) {
  // Token is invalid, expired, or revoked
  log.Printf("Token validation failed: %v", err)
  return
}

orgId := result.TokenInfo.OrganizationId
userId := result.TokenInfo.GetUserId()   // *string — nil for org-scoped tokens
claims := result.TokenInfo.CustomClaims
```

   ### Java

```java
import java.util.Map;
import com.scalekit.exceptions.TokenInvalidException;
import com.scalekit.grpc.scalekit.v1.tokens.ValidateTokenResponse;

try {
    ValidateTokenResponse result = scalekitClient.tokens().validate(opaqueToken);

    String orgId = result.getTokenInfo().getOrganizationId();
    String userId = result.getTokenInfo().getUserId();
    Map claims = result.getTokenInfo().getCustomClaimsMap();
} catch (TokenInvalidException e) {
    // Token is invalid, expired, or revoked
    System.err.println("Token validation failed: " + e.getMessage());
}
```

   

   If the API key is invalid, expired, or has been revoked, validation fails with a specific error that you can catch and handle in your code. This makes it easy to reject unauthorized requests in your API middleware.

   ### Access roles and organization details

   Beyond the basic organization and user information, the validation response also includes any roles assigned to the user and external identifiers you've configured for the organization. These are useful for making authorization decisions without additional database lookups.

   
   ### Node.js

```javascript
try {
  const result = await scalekit.token.validateToken(opaqueToken);

  // Roles assigned to the user
  const roles = result.tokenInfo?.roles;

  // External identifiers for mapping to your system
  const externalOrgId = result.tokenInfo?.organizationExternalId;
  const externalUserId = result.tokenInfo?.userExternalId;
} catch (error) {
  if (error instanceof ScalekitValidateTokenFailureException) {
    console.error('Token validation failed:', error.message);
  }
}
```

   ### Python

```python
try:
    result = scalekit_client.tokens.validate_token(token=opaque_token)

    # Roles assigned to the user
    roles = result.token_info.roles

    # External identifiers for mapping to your system
    external_org_id = result.token_info.organization_external_id
    external_user_id = result.token_info.user_external_id
except ScalekitValidateTokenFailureException:
    print("Token validation failed")
```

   ### Go

```go
result, err := scalekitClient.Token().ValidateToken(ctx, opaqueToken)
if errors.Is(err, scalekit.ErrTokenValidationFailed) {
  log.Printf("Token validation failed: %v", err)
  return
}

// Roles assigned to the user
roles := result.TokenInfo.Roles

// External identifiers for mapping to your system
externalOrgId := result.TokenInfo.OrganizationExternalId
externalUserId := result.TokenInfo.GetUserExternalId()  // *string — nil if no external ID
```

   ### Java

```java
import java.util.List;
import com.scalekit.exceptions.TokenInvalidException;
import com.scalekit.grpc.scalekit.v1.tokens.ValidateTokenResponse;

try {
    ValidateTokenResponse result = scalekitClient.tokens().validate(opaqueToken);

    // Roles assigned to the user
    List roles = result.getTokenInfo().getRolesList();

    // External identifiers for mapping to your system
    String externalOrgId = result.getTokenInfo().getOrganizationExternalId();
    String externalUserId = result.getTokenInfo().getUserExternalId();
} catch (TokenInvalidException e) {
    System.err.println("Token validation failed: " + e.getMessage());
}
```

   

   > note
>
> Roles are available when you use [Full Stack Authentication](/authenticate/fsa/quickstart/) with [role-based access control](/authenticate/authz/overview/). Assign roles to users through the Scalekit dashboard or API.

   ### Access custom metadata

   If you attached custom claims when creating the API key, they come back in every validation response. This is a convenient way to make fine-grained authorization decisions — like restricting access by team or environment — without hitting your database.

   
   ### Node.js

```javascript
try {
  const result = await scalekit.token.validateToken(opaqueToken);

  const team = result.tokenInfo?.customClaims?.team;
  const environment = result.tokenInfo?.customClaims?.environment;

  // Use metadata for authorization
  if (environment !== 'production') {
    return res.status(403).json({ error: 'Production access required' });
  }
} catch (error) {
  if (error instanceof ScalekitValidateTokenFailureException) {
    console.error('Token validation failed:', error.message);
  }
}
```

   ### Python

```python
try:
    result = scalekit_client.tokens.validate_token(token=opaque_token)

    team = result.token_info.custom_claims.get("team")
    environment = result.token_info.custom_claims.get("environment")

    # Use metadata for authorization
    if environment != "production":
        return jsonify({"error": "Production access required"}), 403
except ScalekitValidateTokenFailureException:
    print("Token validation failed")
```

   ### Go

```go
result, err := scalekitClient.Token().ValidateToken(ctx, opaqueToken)
if errors.Is(err, scalekit.ErrTokenValidationFailed) {
  log.Printf("Token validation failed: %v", err)
  return
}

team := result.TokenInfo.CustomClaims["team"]
environment := result.TokenInfo.CustomClaims["environment"]

// Use metadata for authorization
if environment != "production" {
  c.JSON(403, gin.H{"error": "Production access required"})
  return
}
```

   ### Java

```java
import java.util.Map;
import com.scalekit.exceptions.TokenInvalidException;
import com.scalekit.grpc.scalekit.v1.tokens.ValidateTokenResponse;

try {
    ValidateTokenResponse result = scalekitClient.tokens().validate(opaqueToken);

    String team = result.getTokenInfo().getCustomClaimsMap().get("team");
    String environment = result.getTokenInfo().getCustomClaimsMap().get("environment");

    // Use metadata for authorization
    if (!"production".equals(environment)) {
        return ResponseEntity.status(403).body(Map.of("error", "Production access required"));
    }
} catch (TokenInvalidException e) {
    System.err.println("Token validation failed: " + e.getMessage());
}
```

   

4. ## List tokens

   You can retrieve all active API keys for an organization at any time. The response supports pagination for large result sets, and you can filter by user to find keys scoped to a specific person.

   
   ### Node.js

```javascript
try {
  // List tokens for an organization
  const response = await scalekit.token.listTokens(organizationId, {
    pageSize: 10,
  });

  for (const token of response.tokens) {
    console.log(token.tokenId, token.description);
  }

  // Paginate through results
  if (response.nextPageToken) {
    const nextPage = await scalekit.token.listTokens(organizationId, {
      pageSize: 10,
      pageToken: response.nextPageToken,
    });
  }

  // Filter tokens by user
  const userTokens = await scalekit.token.listTokens(organizationId, {
    userId: 'usr_12345',
  });
} catch (error) {
  console.error('Failed to list tokens:', error.message);
}
```

   ### Python

```python
try:
    # List tokens for an organization
    response = scalekit_client.tokens.list_tokens(
        organization_id=organization_id,
        page_size=10,
    )

    for token in response.tokens:
        print(token.token_id, token.description)

    # Paginate through results
    if response.next_page_token:
        next_page = scalekit_client.tokens.list_tokens(
            organization_id=organization_id,
            page_size=10,
            page_token=response.next_page_token,
        )

    # Filter tokens by user
    user_tokens = scalekit_client.tokens.list_tokens(
        organization_id=organization_id,
        user_id="usr_12345",
    )
except Exception as e:
    print(f"Failed to list tokens: {e}")
```

   ### Go

```go
// List tokens for an organization
response, err := scalekitClient.Token().ListTokens(
  ctx, organizationId, scalekit.ListTokensOptions{
    PageSize: 10,
  },
)
if err != nil {
  log.Printf("Failed to list tokens: %v", err)
  return
}

for _, token := range response.Tokens {
  fmt.Println(token.TokenId, token.GetDescription())
}

// Paginate through results
if response.NextPageToken != "" {
  nextPage, err := scalekitClient.Token().ListTokens(
    ctx, organizationId, scalekit.ListTokensOptions{
      PageSize:  10,
      PageToken: response.NextPageToken,
    },
  )
  if err != nil {
    log.Printf("Failed to fetch next page: %v", err)
    return
  }
  _ = nextPage // process nextPage.Tokens
}

// Filter tokens by user
userTokens, err := scalekitClient.Token().ListTokens(
  ctx, organizationId, scalekit.ListTokensOptions{
    UserId: "usr_12345",
  },
)
if err != nil {
  log.Printf("Failed to list user tokens: %v", err)
  return
}
_ = userTokens // process userTokens.Tokens
```

   ### Java

```java
import com.scalekit.grpc.scalekit.v1.tokens.ListTokensResponse;
import com.scalekit.grpc.scalekit.v1.tokens.Token;

try {
    ListTokensResponse response = scalekitClient.tokens().list(organizationId, 10, null);
    for (Token token : response.getTokensList()) {
        System.out.println(token.getTokenId() + " " + token.getDescription());
    }
} catch (Exception e) {
    System.err.println("Failed to list tokens: " + e.getMessage());
}

try {
    ListTokensResponse response = scalekitClient.tokens().list(organizationId, 10, null);
    if (!response.getNextPageToken().isEmpty()) {
        ListTokensResponse nextPage = scalekitClient.tokens().list(
            organizationId, 10, response.getNextPageToken()
        );
    }
} catch (Exception e) {
    System.err.println("Failed to paginate tokens: " + e.getMessage());
}

try {
    ListTokensResponse userTokens = scalekitClient.tokens().list(
        organizationId, "usr_12345", 10, null
    );
} catch (Exception e) {
    System.err.println("Failed to list user tokens: " + e.getMessage());
}
```

   

   The response includes `totalCount` for the total number of matching tokens and `nextPageToken` / `prevPageToken` cursors for navigating pages.

5. ## Invalidate a token

   When you need to revoke an API key — for example, when an employee leaves or you suspect credentials have been compromised — you can invalidate it through Scalekit. Revocation takes effect instantly: the very next validation request for that key will fail.

   This operation is **idempotent**, so calling invalidate on an already-revoked key succeeds without error.

   
   ### Node.js

```javascript
try {
  // Invalidate by API key string
  await scalekit.token.invalidateToken(opaqueToken);

  // Or invalidate by token_id (useful when you store tokenId for lifecycle management)
  await scalekit.token.invalidateToken(tokenId);
} catch (error) {
  console.error('Failed to invalidate token:', error.message);
}
```

   ### Python

```python
try:
    # Invalidate by API key string
    scalekit_client.tokens.invalidate_token(token=opaque_token)

    # Or invalidate by token_id (useful when you store token_id for lifecycle management)
    scalekit_client.tokens.invalidate_token(token=token_id)
except Exception as e:
    print(f"Failed to invalidate token: {e}")
```

   ### Go

```go
// Invalidate by API key string
if err := scalekitClient.Token().InvalidateToken(ctx, opaqueToken); err != nil {
  log.Printf("Failed to invalidate token: %v", err)
}

// Or invalidate by token_id (useful when you store tokenId for lifecycle management)
if err := scalekitClient.Token().InvalidateToken(ctx, tokenId); err != nil {
  log.Printf("Failed to invalidate token: %v", err)
}
```

   ### Java

```java
try {
    // Invalidate by API key string
    scalekitClient.tokens().invalidate(opaqueToken);

    // Or invalidate by token_id (useful when you store tokenId for lifecycle management)
    scalekitClient.tokens().invalidate(tokenId);
} catch (Exception e) {
    System.err.println("Failed to invalidate token: " + e.getMessage());
}
```

   

6. ## Protect your API endpoints

   Now let's put it all together. The most common pattern is to add API key validation as middleware in your API server. Extract the Bearer token from the `Authorization` header, validate it through Scalekit, and use the returned context for authorization decisions.

   
   ### Node.js

```javascript title="Express.js"
import { ScalekitValidateTokenFailureException } from '@scalekit-sdk/node';

async function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    // Reject requests without credentials to prevent unauthorized access
    return res.status(401).json({ error: 'Missing authorization token' });
  }

  try {
    // Server-side validation — Scalekit checks token status in real time
    const result = await scalekit.token.validateToken(token);
    // Attach token context to the request for downstream handlers
    req.tokenInfo = result.tokenInfo;
    next();
  } catch (error) {
    if (error instanceof ScalekitValidateTokenFailureException) {
      // Revoked, expired, or malformed tokens are rejected immediately
      return res.status(401).json({ error: 'Invalid or expired token' });
    }
    throw error;
  }
}

// Apply to protected routes
app.get('/api/resources', authenticateToken, (req, res) => {
  const orgId = req.tokenInfo.organizationId;
  // Serve resources scoped to this organization
});
```

   ### Python

```python title="Flask"
from functools import wraps
from flask import request, jsonify, g
from scalekit import ScalekitValidateTokenFailureException

def authenticate_token(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth_header = request.headers.get("Authorization", "")
        if not auth_header.startswith("Bearer "):
            # Reject requests without credentials to prevent unauthorized access
            return jsonify({"error": "Missing authorization token"}), 401

        token = auth_header.split(" ")[1]

        try:
            # Server-side validation — Scalekit checks token status in real time
            result = scalekit_client.tokens.validate_token(token=token)
            # Attach token context for downstream handlers
            g.token_info = result.token_info
        except ScalekitValidateTokenFailureException:
            # Revoked, expired, or malformed tokens are rejected immediately
            return jsonify({"error": "Invalid or expired token"}), 401

        return f(*args, **kwargs)
    return decorated

# Apply to protected routes
@app.route("/api/resources")
@authenticate_token
def get_resources():
    org_id = g.token_info.organization_id
    # Serve resources scoped to this organization
```

   ### Go

```go title="Gin"
func AuthenticateToken(scalekitClient scalekit.Scalekit) gin.HandlerFunc {
  return func(c *gin.Context) {
    authHeader := c.GetHeader("Authorization")
    if !strings.HasPrefix(authHeader, "Bearer ") {
      // Reject requests without credentials to prevent unauthorized access
      c.JSON(401, gin.H{"error": "Missing authorization token"})
      c.Abort()
      return
    }

    token := strings.TrimPrefix(authHeader, "Bearer ")

    // Server-side validation — Scalekit checks token status in real time
    result, err := scalekitClient.Token().ValidateToken(c.Request.Context(), token)
    if err != nil {
      if errors.Is(err, scalekit.ErrTokenValidationFailed) {
        // Revoked, expired, or malformed tokens are rejected immediately
        c.JSON(401, gin.H{"error": "Invalid or expired token"})
      } else {
        // Surface transport or unexpected errors as 500
        c.JSON(500, gin.H{"error": "Internal server error"})
      }
      c.Abort()
      return
    }

    // Attach token context for downstream handlers
    c.Set("tokenInfo", result.TokenInfo)
    c.Next()
  }
}

// Apply to protected routes
r.GET("/api/resources", AuthenticateToken(scalekitClient), func(c *gin.Context) {
  tokenInfo := c.MustGet("tokenInfo").(*scalekit.TokenInfo)
  orgId := tokenInfo.OrganizationId
  // Serve resources scoped to this organization
})
```

   ### Java

```java title="Spring Boot"
import com.scalekit.exceptions.TokenInvalidException;
import com.scalekit.grpc.scalekit.v1.tokens.Token;
import com.scalekit.grpc.scalekit.v1.tokens.ValidateTokenResponse;

@Component
public class TokenAuthFilter extends OncePerRequestFilter {
    private final ScalekitClient scalekitClient;

    public TokenAuthFilter(ScalekitClient scalekitClient) {
        this.scalekitClient = scalekitClient;
    }

    @Override
    protected void doFilterInternal(
        HttpServletRequest request,
        HttpServletResponse response,
        FilterChain filterChain
    ) throws ServletException, IOException {
        String authHeader = request.getHeader("Authorization");
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            // Reject requests without credentials to prevent unauthorized access
            response.sendError(401, "Missing authorization token");
            return;
        }

        String token = authHeader.substring(7);

        try {
            // Server-side validation — Scalekit checks token status in real time
            ValidateTokenResponse result = scalekitClient.tokens().validate(token);
            // Attach token context for downstream handlers
            request.setAttribute("tokenInfo", result.getTokenInfo());
            filterChain.doFilter(request, response);
        } catch (TokenInvalidException e) {
            // Revoked, expired, or malformed tokens are rejected immediately
            response.sendError(401, "Invalid or expired token");
        }
    }
}

// Access in your controller
@GetMapping("/api/resources")
public ResponseEntity<?> getResources(HttpServletRequest request) {
    Token tokenInfo = (Token) request.getAttribute("tokenInfo");
    String orgId = tokenInfo.getOrganizationId();
    // Serve resources scoped to this organization
}
```

   

   ### Using validation context for data filtering

   After validation succeeds, your middleware has access to the organization and (optionally) user context. Use this context to filter the data your endpoint returns — no additional database queries needed.

   **For organization-scoped keys**: Extract the organization ID from the validation response. Your endpoint then returns resources belonging to that organization. If a customer authenticates with an organization-scoped key, they get access to all their workspace data.

   **For user-scoped keys**: Extract both organization ID and user ID. Filter your query to return only resources belonging to that user within the organization. If a team member authenticates with a user-scoped key, they see only their assigned tasks, their owned projects, or their allocated resources — depending on your application logic.

   The validation response is your source of truth. Trust the organization and user context it provides, and use it to build your authorization queries without additional lookups.

   Here are a few tips to help you get the most out of API keys in production:

   - **Store API keys securely**: Treat API keys like passwords. Store them in encrypted secrets managers or environment variables. Never log keys, commit them to version control, or expose them in client-side code.
   - **Set expiry for time-limited access**: Use the `expiry` parameter for keys that should automatically become invalid after a set period. This limits the blast radius if a key is compromised.
   - **Use custom claims for context**: Attach metadata like `team`, `environment`, or `service` as custom claims. Your API middleware can use these claims for fine-grained authorization without additional database lookups.
   - **Rotate keys safely**: To rotate an API key, create a new key, update the consuming service to use the new key, verify the new key works, then invalidate the old key. This avoids downtime during rotation.

   You now have everything you need to issue, validate, and manage API keys in your application.


---

## More Scalekit documentation

| Resource | What it contains | When to use it |
|----------|-----------------|----------------|
| [/llms.txt](/llms.txt) | Structured index with routing hints per product area | Start here — find which documentation set covers your topic before loading full content |
| [/llms-full.txt](/llms-full.txt) | Complete documentation for all Scalekit products in one file | Use when you need exhaustive context across multiple products or when the topic spans several areas |
| [sitemap-0.xml](https://docs.scalekit.com/sitemap-0.xml) | Full URL list of every documentation page | Use to discover specific page URLs you can fetch for targeted, page-level answers |
