adding-api-key-auth
Adding API Key Auth (Scalekit)
Flow overview
Your app creates token (org or user scoped) → Scalekit returns key + tokenId →
Customer stores key → API client sends Bearer key → Your server validates →
Scalekit returns org/user context → Filter data accordingly
The plain-text API key is returned only once at creation. Scalekit never stores it.
1. Initialize the client
# Python
from scalekit import ScalekitClient
import os
scalekit_client = ScalekitClient(
env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"],
client_id=os.environ["SCALEKIT_CLIENT_ID"],
client_secret=os.environ["SCALEKIT_CLIENT_SECRET"],
)
// Node.js
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
);
// Go
scalekitClient := scalekit.NewScalekitClient(
os.Getenv("SCALEKIT_ENVIRONMENT_URL"),
os.Getenv("SCALEKIT_CLIENT_ID"),
os.Getenv("SCALEKIT_CLIENT_SECRET"),
)
// Java
ScalekitClient scalekitClient = new ScalekitClient(
System.getenv("SCALEKIT_ENVIRONMENT_URL"),
System.getenv("SCALEKIT_CLIENT_ID"),
System.getenv("SCALEKIT_CLIENT_SECRET")
);
Required env vars: SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, SCALEKIT_CLIENT_SECRET.
2. Create a token
Organization-scoped (default)
Grants access to all resources in the organization's workspace. Use for service-to-service integrations (CI/CD, partner integrations, internal tooling).
# Python
response = scalekit_client.tokens.create_token(
organization_id=organization_id,
description="CI/CD pipeline token",
)
opaque_token = response.token # show to user once; never stored by Scalekit
token_id = response.token_id # format: apit_xxxxx — use for lifecycle ops
// Node.js
const response = await scalekit.token.createToken(organizationId, {
description: 'CI/CD pipeline token',
});
const opaqueToken = response.token;
const tokenId = response.tokenId;
// Go
response, err := scalekitClient.Token().CreateToken(
ctx, organizationId, scalekit.CreateTokenOptions{
Description: "CI/CD pipeline token",
},
)
opaqueToken := response.Token
tokenId := response.TokenId
// Java
CreateTokenResponse response = scalekitClient.tokens().create(organizationId);
String opaqueToken = response.getToken();
String tokenId = response.getTokenId();
User-scoped (optional userId)
Adds user context so your API can filter data to only that user's resources (personal access tokens, per-user audit trails, user-level rate limiting). Attach customClaims for fine-grained authz without extra DB lookups.
# Python
response = scalekit_client.tokens.create_token(
organization_id=organization_id,
user_id="usr_12345",
custom_claims={"team": "engineering", "environment": "production"},
description="Deployment service token",
)
// Node.js
const response = await scalekit.token.createToken(organizationId, {
userId: 'usr_12345',
customClaims: { team: 'engineering', environment: 'production' },
description: 'Deployment service token',
});
// Go
response, err := scalekitClient.Token().CreateToken(
ctx, organizationId, scalekit.CreateTokenOptions{
UserId: "usr_12345",
CustomClaims: map[string]string{"team": "engineering", "environment": "production"},
Description: "Deployment service token",
},
)
// Java
Map<String, String> claims = Map.of("team", "engineering", "environment", "production");
CreateTokenResponse response = scalekitClient.tokens().create(
organizationId, "usr_12345", claims, null, "Deployment service token"
);
Response fields:
| Field | Description |
|---|---|
token |
Plain-text API key. Returned only at creation. |
token_id |
Stable ID (apit_xxxxx) for list/invalidate operations. |
token_info |
Metadata: org, user, custom claims, timestamps. |
3. Validate a token
Call this on every incoming API request. Returns org/user context; throws on invalid, expired, or revoked keys.
# 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 # empty for org-scoped keys
claims = result.token_info.custom_claims
roles = result.token_info.roles # populated if RBAC is configured
ext_org = result.token_info.organization_external_id
except ScalekitValidateTokenFailureException:
return 401
// Node.js
import { ScalekitValidateTokenFailureException } from '@scalekit-sdk/node';
try {
const result = await scalekit.token.validateToken(opaqueToken);
const { organizationId, userId, customClaims, roles, organizationExternalId } = result.tokenInfo;
} catch (error) {
if (error instanceof ScalekitValidateTokenFailureException) return res.status(401).end();
throw error;
}
// Go
result, err := scalekitClient.Token().ValidateToken(ctx, opaqueToken)
if errors.Is(err, scalekit.ErrTokenValidationFailed) {
c.JSON(401, gin.H{"error": "Invalid or expired token"})
return
}
orgId := result.TokenInfo.OrganizationId
userId := result.TokenInfo.GetUserId() // *string — nil for org-scoped tokens
claims := result.TokenInfo.CustomClaims
// Java
try {
ValidateTokenResponse result = scalekitClient.tokens().validate(opaqueToken);
String orgId = result.getTokenInfo().getOrganizationId();
String userId = result.getTokenInfo().getUserId();
Map<String, String> claims = result.getTokenInfo().getCustomClaimsMap();
} catch (TokenInvalidException e) {
response.sendError(401);
}
4. List tokens
Supports pagination and optional user filter.
# Python — list with pagination
response = scalekit_client.tokens.list_tokens(
organization_id=organization_id,
page_size=10,
)
for token in response.tokens:
print(token.token_id, token.description)
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 by user
user_tokens = scalekit_client.tokens.list_tokens(
organization_id=organization_id,
user_id="usr_12345",
)
// Node.js
const response = await scalekit.token.listTokens(organizationId, { pageSize: 10 });
if (response.nextPageToken) {
const next = await scalekit.token.listTokens(organizationId, {
pageSize: 10, pageToken: response.nextPageToken
});
}
const userTokens = await scalekit.token.listTokens(organizationId, { userId: 'usr_12345' });
5. Invalidate a token
Revocation is instant — the next validation for that key fails immediately. The operation is idempotent: safe to call on already-revoked keys.
# Python — by token string or token_id
scalekit_client.tokens.invalidate_token(token=opaque_token)
# or
scalekit_client.tokens.invalidate_token(token=token_id)
// Node.js
await scalekit.token.invalidateToken(opaqueToken); // or tokenId
// Go
_ = scalekitClient.Token().InvalidateToken(ctx, opaqueToken) // or tokenId
// Java
scalekitClient.tokens().invalidate(opaqueToken); // or tokenId
6. Middleware pattern (protect endpoints)
# Python — Flask decorator
from functools import wraps
from flask import request, jsonify, g
from scalekit import ScalekitValidateTokenFailureException
def authenticate_token(f):
@wraps(f)
def wrapper(*args, **kwargs):
auth = request.headers.get("Authorization", "")
if not auth.startswith("Bearer "):
return jsonify({"error": "Missing authorization token"}), 401
try:
result = scalekit_client.tokens.validate_token(token=auth.split(" ", 1)[1])
g.token_info = result.token_info
except ScalekitValidateTokenFailureException:
return jsonify({"error": "Invalid or expired token"}), 401
return f(*args, **kwargs)
return wrapper
@app.route("/api/resources")
@authenticate_token
def get_resources():
org_id = g.token_info.organization_id # always present
user_id = g.token_info.user_id # present only for user-scoped keys
# query DB filtered by org_id (and user_id if set)
// Node.js — Express middleware
async function authenticateToken(req, res, next) {
const token = (req.headers.authorization || '').replace('Bearer ', '');
if (!token) return res.status(401).json({ error: 'Missing authorization token' });
try {
const result = await scalekit.token.validateToken(token);
req.tokenInfo = result.tokenInfo;
next();
} catch (error) {
if (error instanceof ScalekitValidateTokenFailureException)
return res.status(401).json({ error: 'Invalid or expired token' });
throw error;
}
}
app.get('/api/resources', authenticateToken, (req, res) => {
const { organizationId, userId } = req.tokenInfo;
});
// Go — Gin middleware
func AuthenticateToken(sc scalekit.Scalekit) gin.HandlerFunc {
return func(c *gin.Context) {
token := strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ")
if token == "" {
c.JSON(401, gin.H{"error": "Missing authorization token"}); c.Abort(); return
}
result, err := sc.Token().ValidateToken(c.Request.Context(), token)
if err != nil {
c.JSON(401, gin.H{"error": "Invalid or expired token"}); c.Abort(); return
}
c.Set("tokenInfo", result.TokenInfo)
c.Next()
}
}
Data filtering pattern
| Key type | Filter query by | Example use case |
|---|---|---|
| Organization-scoped | organizationId only |
All workspace contacts in a CRM |
| User-scoped | organizationId + userId |
Only tasks assigned to the calling user |
| Custom claims | Claims from customClaims map |
Restrict by environment, team, etc. |
Key rules
- Show
tokenonce: Display to user at creation, then discard — Scalekit cannot retrieve it. - Validate server-side on every request: Never trust unverified tokens; call
validateTokeneach time. - Use
token_idfor lifecycle ops: Storetoken_id(not the key itself) for list/invalidate workflows. - Rotate safely: Create new key → update consumer → verify → invalidate old key (avoids downtime).
- Use
expiryfor time-limited access: Limits blast radius if a key is compromised. - Never log or commit keys: Treat API keys like passwords — use encrypted secrets managers or env vars.
More from scalekit-inc/skills
setup-scalekit
Use when a developer is new to Scalekit and needs guidance on where to start, doesn't know which auth plugin or skill to choose, wants to connect an AI agent or agentic workflow to third-party services (Gmail, Slack, Notion, Google Calendar), needs OAuth or tool-calling auth for agents, wants to add authentication to a project but hasn't chosen an approach yet, or needs to install the Scalekit plugin for their AI coding tool (Claude Code, Codex, Copilot CLI, Cursor, or other agents).
11implementing-scalekit-fsa
Implements Scalekit full-stack authentication (FSA) including sign-up, login, logout, and secure session management using JWT tokens. Use when building or integrating user authentication with the Scalekit SDK across Node.js, Python, Go, or Java — or when the user asks about auth flows, OAuth callbacks, token refresh, or session handling with Scalekit.
4integrating-agent-auth
Integrates Scalekit Agent Auth into a project to handle OAuth flows, token storage, and automatic refresh for third-party services (Gmail, Slack, Notion, Calendar). Use when a user needs to connect to an external service, authorize OAuth access, fetch access or refresh tokens, or execute API calls on behalf of a user.
4adding-mcp-oauth
Guides users through adding OAuth 2.1 authorization to Model Context Protocol (MCP) servers using Scalekit. Use when setting up MCP servers, implementing authentication for AI hosts like Claude Desktop, Cursor, or VS Code, or when users mention MCP security, OAuth, or Scalekit integration.
3modular-sso
Implements complete SSO and authentication flows using Scalekit. Handles modular SSO, IdP-initiated login, user session management, and enterprise customer onboarding. Use when adding authentication, SSO, SAML, OIDC, or user login to applications.
3sk-actions-custom-provider
Create or review Scalekit custom providers/connectors for proxy-only usage. Use this skill when the task is to gather API docs, infer whether a connector is OAuth, Basic, Bearer, or API Key, determine required tracked fields like domain or version, generate provider JSON, check for existing custom providers, show update diffs, run approved create or update curls, and print resolved delete curls.
3