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.
-
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:
Express.js 2 collapsed linesimport { ScalekitClient } from '@scalekit-sdk/node';const scalekit = new ScalekitClient(process.env.SCALEKIT_ENVIRONMENT_URL,process.env.SCALEKIT_CLIENT_ID,process.env.SCALEKIT_CLIENT_SECRET);Flask 2 collapsed linesimport osfrom scalekit import ScalekitClientscalekit_client = ScalekitClient(env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"],client_id=os.environ["SCALEKIT_CLIENT_ID"],client_secret=os.environ["SCALEKIT_CLIENT_SECRET"],)Gin 2 collapsed linesimport 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"),)Spring Boot 2 collapsed linesimport 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”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.
try {const response = await scalekit.token.createToken(organizationId, {description: 'CI/CD pipeline token',});// Store securely — this value cannot be retrieved again after creationconst 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",)opaque_token = response.token # store this securelytoken_id = response.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.TokenIdimport com.scalekit.grpc.scalekit.v1.tokens.CreateTokenResponse;try {CreateTokenResponse response = scalekitClient.tokens().create(organizationId);// Store securely — this value cannot be retrieved again after creationString 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”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
/tasksendpoint. 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.
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.tokentoken_id = user_token.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.TokenIdimport java.util.Map;import com.scalekit.grpc.scalekit.v1.tokens.CreateTokenResponse;try {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 tokenThe API key string. Returned only at creation. token_idAn identifier (format: apit_xxxxx) for referencing the token in management operations.token_infoMetadata 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 revokedconsole.error('Token validation failed:', error.message);}}from scalekit import ScalekitValidateTokenFailureExceptiontry:result = scalekit_client.tokens.validate_token(token=opaque_token)org_id = result.token_info.organization_iduser_id = result.token_info.user_idclaims = result.token_info.custom_claimsexcept ScalekitValidateTokenFailureException:# Token is invalid, expired, or revokedprint("Token validation failed")result, err := scalekitClient.Token().ValidateToken(ctx, opaqueToken)if errors.Is(err, scalekit.ErrTokenValidationFailed) {// Token is invalid, expired, or revokedlog.Printf("Token validation failed: %v", err)return}orgId := result.TokenInfo.OrganizationIduserId := result.TokenInfo.GetUserId() // *string — nil for org-scoped tokensclaims := result.TokenInfo.CustomClaimsimport 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<String, String> claims = result.getTokenInfo().getCustomClaimsMap();} catch (TokenInvalidException e) {// Token is invalid, expired, or revokedSystem.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 userconst roles = result.tokenInfo?.roles;// External identifiers for mapping to your systemconst 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 userroles = result.token_info.roles# External identifiers for mapping to your systemexternal_org_id = result.token_info.organization_external_idexternal_user_id = result.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.GetUserExternalId() // *string — nil if no external IDimport 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 userList<String> roles = result.getTokenInfo().getRolesList();// External identifiers for mapping to your systemString 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 authorizationif (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.token_info.custom_claims.get("team")environment = result.token_info.custom_claims.get("environment")# Use metadata for authorizationif 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}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 authorizationif (!"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.
try {// 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',});} catch (error) {console.error('Failed to list tokens:', error.message);}try:# List tokens for an organizationresponse = 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 resultsif 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 useruser_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}")// List tokens for an organizationresponse, 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 resultsif 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 useruserTokens, 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.Tokensimport 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
totalCountfor the total number of matching tokens andnextPageToken/prevPageTokencursors 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.
try {// 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);} catch (error) {console.error('Failed to invalidate token:', error.message);}try:# 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)except Exception as e:print(f"Failed to invalidate token: {e}")// Invalidate by API key stringif 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)}try {// 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);} catch (Exception e) {System.err.println("Failed to invalidate token: " + e.getMessage());} -
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
Authorizationheader, validate it through Scalekit, and use the returned context for authorization decisions.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 accessreturn res.status(401).json({ error: 'Missing authorization token' });}try {// Server-side validation — Scalekit checks token status in real timeconst result = await scalekit.token.validateToken(token);// Attach token context to the request for downstream handlersreq.tokenInfo = result.tokenInfo;next();} catch (error) {if (error instanceof ScalekitValidateTokenFailureException) {// Revoked, expired, or malformed tokens are rejected immediatelyreturn 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});Flask from functools import wrapsfrom flask import request, jsonify, gfrom scalekit import ScalekitValidateTokenFailureExceptiondef 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 accessreturn jsonify({"error": "Missing authorization token"}), 401token = auth_header.split(" ")[1]try:# Server-side validation — Scalekit checks token status in real timeresult = scalekit_client.tokens.validate_token(token=token)# Attach token context for downstream handlersg.token_info = result.token_infoexcept ScalekitValidateTokenFailureException:# Revoked, expired, or malformed tokens are rejected immediatelyreturn jsonify({"error": "Invalid or expired token"}), 401return 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 organizationGin 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 accessc.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 timeresult, err := scalekitClient.Token().ValidateToken(c.Request.Context(), token)if err != nil {if errors.Is(err, scalekit.ErrTokenValidationFailed) {// Revoked, expired, or malformed tokens are rejected immediatelyc.JSON(401, gin.H{"error": "Invalid or expired token"})} else {// Surface transport or unexpected errors as 500c.JSON(500, gin.H{"error": "Internal server error"})}c.Abort()return}// Attach token context for downstream handlersc.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})Spring Boot import com.scalekit.exceptions.TokenInvalidException;import com.scalekit.grpc.scalekit.v1.tokens.Token;import com.scalekit.grpc.scalekit.v1.tokens.ValidateTokenResponse;@Componentpublic class TokenAuthFilter extends OncePerRequestFilter {private final ScalekitClient scalekitClient;public TokenAuthFilter(ScalekitClient scalekitClient) {this.scalekitClient = scalekitClient;}@Overrideprotected 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 accessresponse.sendError(401, "Missing authorization token");return;}String token = authHeader.substring(7);try {// Server-side validation — Scalekit checks token status in real timeValidateTokenResponse result = scalekitClient.tokens().validate(token);// Attach token context for downstream handlersrequest.setAttribute("tokenInfo", result.getTokenInfo());filterChain.doFilter(request, response);} catch (TokenInvalidException e) {// Revoked, expired, or malformed tokens are rejected immediatelyresponse.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
Section titled “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
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.