API keys
Issue long-lived, revocable API keys scoped to organizations and users for programmatic access to your APIs
When your customers need to integrate with your APIs — whether for CI/CD pipelines, partner integrations, or internal tooling — they need a straightforward way to authenticate. Scalekit API keys gives you long-lived, revocable bearer credentials that can be issued to your customers for Organizational or User Level access to your APIs.
Unlike JWT-based M2M authentication, API keys do not carry embedded claims and cannot be validated on the client side. The API Keys can be validated via APIs with Scalekit and after server-side validation, the claims are sent when a valid API Key is presented. Revocation of API keys takes effect immediately with no expiry windows or propagation delays. Each key is scoped to an organization and optionally to a specific user, making it easy to build personal access tokens, per-user rate limiting, and audit trails.
In this guide, you’ll learn how to create, validate, list, and revoke API keys using the Scalekit SDK.
Install the SDK
Section titled “Install the SDK”npm install @scalekit-sdk/nodepip install scalekit-sdk-pythongo get -u github.com/scalekit-inc/scalekit-sdk-go/* Gradle users - add the following to your dependencies in build file */implementation "com.scalekit:scalekit-sdk-java:2.0.11"<!-- 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:
2 collapsed lines
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);2 collapsed lines
import osfrom 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"],)2 collapsed lines
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"),)2 collapsed lines
import com.scalekit.ScalekitClient;
ScalekitClient scalekitClient = new ScalekitClient( System.getenv("SCALEKIT_ENVIRONMENT_URL"), System.getenv("SCALEKIT_CLIENT_ID"), System.getenv("SCALEKIT_CLIENT_SECRET"));Create a token
Section titled “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
Section titled “Organization-scoped API key”Create an API key scoped to an organization. This is the most common pattern for service-to-service integrations where the API key represents access on behalf of an entire organization.
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);}try: response = scalekit_client.tokens.create_token( organization_id=organization_id, description="CI/CD pipeline token", )
# SDK returns (response, metadata) tuple — access response at index 0 opaque_token = response[0].token # store this securely token_id = response[0].token_id # format: apit_xxxxxexcept Exception as e: print(f"Failed to create token: {e}")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 creationopaqueToken := response.Token// Stable identifier for management operations (format: apit_xxxxx)tokenId := response.TokenIdtry { 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
Section titled “User-scoped API key”Scope an API key to a specific user within an organization to enable personal access tokens, per-user audit trails, and user-level rate limiting. You can also attach custom claims as key-value metadata.
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);}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[0].token token_id = user_token[0].token_idexcept Exception as e: print(f"Failed to create token: {e}")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.TokentokenId := userToken.TokenIdtry { Map<String, String> 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. |
Validate a token
Section titled “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.
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); }}from scalekit import ScalekitValidateTokenFailureException
try: result = scalekit_client.tokens.validate_token(token=opaque_token)
org_id = result[0].token_info.organization_id user_id = result[0].token_info.user_id claims = result[0].token_info.custom_claimsexcept ScalekitValidateTokenFailureException: # Token is invalid, expired, or revoked print("Token validation failed")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.OrganizationIduserId := result.TokenInfo.UserIdclaims := result.TokenInfo.CustomClaimsimport com.scalekit.exceptions.TokenInvalidException;
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) { // 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
Section titled “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.
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); }}try: result = scalekit_client.tokens.validate_token(token=opaque_token)
# Roles assigned to the user roles = result[0].token_info.roles
# External identifiers for mapping to your system external_org_id = result[0].token_info.organization_external_id external_user_id = result[0].token_info.user_external_idexcept ScalekitValidateTokenFailureException: print("Token validation failed")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 userroles := result.TokenInfo.Roles
// External identifiers for mapping to your systemexternalOrgId := result.TokenInfo.OrganizationExternalIdexternalUserId := result.TokenInfo.UserExternalIdtry { ValidateTokenResponse result = scalekitClient.tokens().validate(opaqueToken);
// Roles assigned to the user List<String> 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());}Access custom metadata
Section titled “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.
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); }}try: result = scalekit_client.tokens.validate_token(token=opaque_token)
team = result[0].token_info.custom_claims.get("team") environment = result[0].token_info.custom_claims.get("environment")
# Use metadata for authorization if environment != "production": return jsonify({"error": "Production access required"}), 403except ScalekitValidateTokenFailureException: print("Token validation failed")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 authorizationif environment != "production" { c.JSON(403, gin.H{"error": "Production access required"}) return}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());}List tokens
Section titled “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.
// List tokens for an organizationconst response = await scalekit.token.listTokens(organizationId, { pageSize: 10,});
for (const token of response.tokens) { console.log(token.tokenId, token.description);}
// Paginate through resultsif (response.nextPageToken) { const nextPage = await scalekit.token.listTokens(organizationId, { pageSize: 10, pageToken: response.nextPageToken, });}
// Filter tokens by userconst userTokens = await scalekit.token.listTokens(organizationId, { userId: 'usr_12345',});# List tokens for an organizationresponse = scalekit_client.tokens.list_tokens( organization_id=organization_id, page_size=10,)
for token in response[0].tokens: print(token.token_id, token.description)
# Paginate through resultsif response[0].next_page_token: next_page = scalekit_client.tokens.list_tokens( organization_id=organization_id, page_size=10, page_token=response[0].next_page_token, )
# Filter tokens by useruser_tokens = scalekit_client.tokens.list_tokens( organization_id=organization_id, user_id="usr_12345",)// List tokens for an organizationresponse, err := scalekitClient.Token().ListTokens( ctx, organizationId, scalekit.ListTokensOptions{ PageSize: 10, },)
for _, token := range response.Tokens { fmt.Println(token.TokenId, token.GetDescription())}
// Paginate through resultsif response.NextPageToken != "" { nextPage, err := scalekitClient.Token().ListTokens( ctx, organizationId, scalekit.ListTokensOptions{ PageSize: 10, PageToken: response.NextPageToken, }, )}
// Filter tokens by useruserTokens, err := scalekitClient.Token().ListTokens( ctx, organizationId, scalekit.ListTokensOptions{ UserId: "usr_12345", },)// List tokens for an organizationListTokensResponse response = scalekitClient.tokens().list(organizationId, 10, null);
for (Token token : response.getTokensList()) { System.out.println(token.getTokenId() + " " + token.getDescription());}
// Paginate through resultsif (!response.getNextPageToken().isEmpty()) { ListTokensResponse nextPage = scalekitClient.tokens().list( organizationId, 10, response.getNextPageToken() );}
// Filter tokens by userListTokensResponse userTokens = scalekitClient.tokens().list( organizationId, "usr_12345", 10, null);The response includes totalCount for the total number of matching tokens and nextPageToken / prevPageToken cursors for navigating pages.
Invalidate a token
Section titled “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.
// Invalidate by API key stringawait scalekit.token.invalidateToken(opaqueToken);
// Or invalidate by token_id (useful when you store tokenId for lifecycle management)await scalekit.token.invalidateToken(tokenId);# Invalidate by API key stringscalekit_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)// Invalidate by API key stringerr := scalekitClient.Token().InvalidateToken(ctx, opaqueToken)
// Or invalidate by token_id (useful when you store tokenId for lifecycle management)err = scalekitClient.Token().InvalidateToken(ctx, tokenId)// Invalidate by API key stringscalekitClient.tokens().invalidate(opaqueToken);
// Or invalidate by token_id (useful when you store tokenId for lifecycle management)scalekitClient.tokens().invalidate(tokenId);Protect your API endpoints
Section titled “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.
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 routesapp.get('/api/resources', authenticateToken, (req, res) => { const orgId = req.tokenInfo.organizationId; // Serve resources scoped to this organization});from functools import wrapsfrom flask import request, jsonify, gfrom 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[0].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_tokendef get_resources(): org_id = g.token_info.organization_id # Serve resources scoped to this organizationfunc 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 errors.Is(err, scalekit.ErrTokenValidationFailed) { // Revoked, expired, or malformed tokens are rejected immediately c.JSON(401, gin.H{"error": "Invalid or expired token"}) c.Abort() return }
// Attach token context for downstream handlers c.Set("tokenInfo", result.TokenInfo) c.Next() }}
// Apply to protected routesr.GET("/api/resources", AuthenticateToken(scalekitClient), func(c *gin.Context) { tokenInfo := c.MustGet("tokenInfo").(*scalekit.TokenInfo) orgId := tokenInfo.OrganizationId // Serve resources scoped to this organization})import com.scalekit.exceptions.TokenInvalidException;
@Componentpublic 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}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
expiryparameter 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, orserviceas 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.