Authentication
Learn how to authenticate requests when integrating Stash Launcher. This guide covers API key authentication for build management, channel management, and token service operations.
Stash Launcher uses API keys to authenticate server-side operations for build management and authentication token flows. All server-side Stash Launcher operations require API key authentication.
When API Keys Are Required
API keys are required for all server-side Stash Launcher operations:
- Build Management - Creating, updating, and querying build artifacts
- Channel Management - Setting which builds are on which channels
- Token Management - Saving authentication tokens for launcher deeplink flows
Creating API Keys
Navigate to API Secrets
Go to Stash Studio → Project Settings → API Secrets.
Create a New API Secret
Click "Create API Secret" or "Add API Secret".
Name Your Key
Enter a descriptive name (e.g., "Launcher Build Service", "Launcher Production").
Copy the Secret
Click "Create" and copy the secret value immediately - it's only shown once.
For detailed information on creating, managing, and securing API keys, see the API Keys guide.
Using API Keys
Authentication Header
Include your API key in the X-Stash-Api-Key header for all authenticated requests:
X-Stash-Api-Key: your-api-key-secret-hereMake sure API key requests are made from your server, not the client, so the API key remains private.
Build Management Operations
Get Build Status
const axios = require('axios');
async function getBuildStatus() {
const response = await axios.get(
'https://api.stash.gg/api/v1/launcher/status',
{
headers: {
'X-Stash-Api-Key': process.env.STASH_API_KEY
}
}
);
return response.data;
}Create Build and Generate Upload URL
async function createBuildAndGetUploadUrl(buildName, fileInfo) {
const response = await axios.post(
'https://api.stash.gg/api/v1/launcher/builds/create',
{
name: buildName,
file: {
name: fileInfo.name,
crc32c: fileInfo.crc32c
}
},
{
headers: {
'X-Stash-Api-Key': process.env.STASH_API_KEY,
'Content-Type': 'application/json'
}
}
);
return {
buildId: response.data.id,
uploadUrl: response.data.signed_resumable_post_url,
headers: response.data.headers
};
}Get Build Artifact
async function getBuildArtifact(buildId) {
const response = await axios.get(
`https://api.stash.gg/api/v1/launcher/builds/${buildId}`,
{
headers: {
'X-Stash-Api-Key': process.env.STASH_API_KEY
}
}
);
return response.data;
}Update Build Artifact
async function updateBuildArtifact(buildId, executable) {
const response = await axios.patch(
`https://api.stash.gg/api/v1/launcher/builds/${buildId}`,
{
update: {
executable: {
win_exe: executable.windows, // or mac_exe for Mac
}
}
},
{
headers: {
'X-Stash-Api-Key': process.env.STASH_API_KEY,
'Content-Type': 'application/json'
}
}
);
return response.data;
}Complete Build Upload Flow
const axios = require('axios');
const fs = require('fs');
const FormData = require('form-data');
async function uploadBuild(buildName, buildFilePath) {
const apiKey = process.env.STASH_API_KEY;
// Step 1: Create build and get upload URL
const fileStats = fs.statSync(buildFilePath);
const fileBuffer = fs.readFileSync(buildFilePath);
const crc32c = calculateCRC32C(fileBuffer); // You'll need a CRC32C library
const createResponse = await axios.post(
'https://api.stash.gg/api/v1/launcher/builds/create',
{
name: buildName,
file: {
name: path.basename(buildFilePath),
crc32c: crc32c
}
},
{
headers: {
'X-Stash-Api-Key': apiKey,
'Content-Type': 'application/json'
}
}
);
const { id: buildId, signed_resumable_post_url: uploadUrl, headers: uploadHeaders } = createResponse.data;
// Step 2: Upload file to signed URL
const formData = new FormData();
formData.append('file', fs.createReadStream(buildFilePath));
await axios.post(uploadUrl, formData, {
headers: {
...uploadHeaders,
...formData.getHeaders()
}
});
// Step 3: Wait for processing (poll GetBuildArtifact)
let build = null;
let attempts = 0;
while (attempts < 30) { // Poll for up to 5 minutes
await new Promise(resolve => setTimeout(resolve, 10000)); // Wait 10 seconds
const getResponse = await axios.get(
`https://api.stash.gg/api/v1/launcher/builds/${buildId}`,
{
headers: {
'X-Stash-Api-Key': apiKey
}
}
);
build = getResponse.data;
if (build.post_process_metadata) {
break; // Processing complete
}
attempts++;
}
if (!build.post_process_metadata) {
throw new Error('Build processing timed out');
}
// Step 4: Update build with executable paths
const executables = extractExecutables(build.post_process_metadata);
await axios.patch(
`https://api.stash.gg/api/v1/launcher/builds/${buildId}`,
{
update: {
executable: {
win_exe: executables.windows,
mac_exe: executables.mac
}
}
},
{
headers: {
'X-Stash-Api-Key': apiKey,
'Content-Type': 'application/json'
}
}
);
return buildId;
}Channel Management
Set Build Artifact Channel
async function setBuildArtifactChannel(partnerId, shopId, channelId, platform, artifactId) {
const response = await axios.post(
'https://api.stash.gg/api/v1/launcher/channels/set',
{
partner_id: partnerId,
shop_id: shopId,
channel_id: channelId,
platform: platform, // 'WIN_PLATFORM' or 'MAC_PLATFORM'
artifact_id: artifactId
},
{
headers: {
'X-Stash-Api-Key': process.env.STASH_API_KEY,
'Content-Type': 'application/json'
}
}
);
return response.data;
}Token Management for Deeplink Flows
Overview
The SDK Token Service allows your game backend to temporarily save authentication tokens that can be retrieved via launcher deeplinks. This enables seamless authentication when users click launcher links.
Flow
- User authenticates in game → Game backend has access/refresh tokens
- Game backend saves tokens → Calls
SaveTokenswith API key - Stash returns code_challenge → Short code that can be passed via deeplink
- Game opens launcher with deeplink → Includes code_challenge
- Launcher retrieves tokens → Calls
GetTokenswith code_challenge - Launcher uses tokens → Authenticates user automatically
Save Tokens Example
// Called by your game backend after user authenticates
async function saveTokensForLauncher(accessToken, refreshToken) {
const response = await axios.post(
'https://api.stash.gg/api/v1/sdk/launcher/tokens/save',
{
access_token: accessToken,
refresh_token: refreshToken
},
{
headers: {
'X-Stash-Api-Key': process.env.STASH_API_KEY,
'Content-Type': 'application/json'
}
}
);
// Returns code_challenge to include in launcher deeplink
// e.g., "stash-launcher://auth?code=abc123"
return response.data.code_challenge;
}The code_challenge is scoped to your shop and expires after 30 minutes. It can only be used once.
Common Pitfalls
Build Processing Timeouts
Common Mistake: Not waiting long enough for build processing to complete
Solution: Build processing can take several minutes. Poll the build status every 10-15 seconds and allow up to 5 minutes for processing. Check post_process_metadata to confirm completion.
Common Mistake: Updating build with executable paths before processing completes
Solution: Wait for post_process_metadata to be available before updating the build with executable paths.
Channel Assignment Mistakes
Common Mistake: Assigning a build to a channel before it's fully processed
Solution: Only assign builds to channels after processing is complete and executable paths are set.
Common Mistake: Using incorrect platform identifier
Solution: Use exact platform identifiers: WIN_PLATFORM or MAC_PLATFORM (case-sensitive).
Token Service Deeplink Flow Issues
Common Mistake: Using expired or already-used code_challenge
Solution: Code challenges expire after 30 minutes and can only be used once. Generate a new code_challenge for each authentication flow.
Common Mistake: Not including code_challenge in launcher deeplink
Solution: Ensure the deeplink includes the code_challenge returned from SaveTokens (e.g., stash-launcher://auth?code=abc123).
Build Upload Failures
Common Mistake: Uploading file before creating the build record
Solution: Always create the build first to get the signed upload URL, then upload the file to that URL.
Common Mistake: Not including required headers in file upload
Solution: Include all headers returned from the build creation response when uploading the file.
Common Mistake: Incorrect CRC32C checksum
Solution: Calculate and include the correct CRC32C checksum when creating the build. Mismatched checksums will cause upload failures.
Error Handling
Common Authentication Errors
401 Unauthenticated
Error: invalid auth or X-Stash-Api-Key header is required
Solution: Ensure the X-Stash-Api-Key header is included in your request.
403 Permission Denied
Error: invalid key or PermissionDenied
Causes:
- API key is invalid or deleted
- API key doesn't belong to the shop
Solution: Verify the API key in Studio and ensure it matches your shop.
Build-Specific Errors
Build Not Found
Error: NotFound when querying a build
Causes:
- Build ID doesn't exist
- Build belongs to a different shop
Solution: Verify the build ID and ensure it belongs to your shop.
Processing Not Complete
When polling for build processing, if post_process_metadata is not available:
- Wait longer (processing can take several minutes)
- Check build status in Studio
- Verify the uploaded file was valid
Security Best Practices
1. Store API Keys Securely
DO:
- Store API keys in environment variables
- Use secrets management services (AWS Secrets Manager, Azure Key Vault, etc.)
- Never commit API keys to version control
DON'T:
- Hardcode API keys in source code
- Share API keys via email or chat
- Expose API keys in client-side code
2. Use Different Keys for Different Environments
- Development/Testing: Test environment keys
- Staging: Staging environment keys
- Production: Production environment keys
This allows you to:
- Revoke test keys without affecting production
- Monitor usage per environment
- Limit blast radius if a key is compromised
3. Rotate Keys Regularly
- Rotate keys every 90 days
- Or immediately if compromised
- Create new key before deleting old one
4. Monitor Usage
- Log all API key usage
- Set up alerts for unusual activity
- Track which services use which keys
5. Least Privilege
- Create separate keys for different services if needed
- Delete unused keys
- Use descriptive names to identify key purpose
Complete Integration Example
class LauncherService {
constructor(apiKey, partnerId, shopId) {
this.apiKey = apiKey;
this.partnerId = partnerId;
this.shopId = shopId;
this.baseUrl = 'https://api.stash.gg/api/v1/launcher';
}
async getStatus() {
const response = await axios.get(`${this.baseUrl}/status`, {
headers: { 'X-Stash-Api-Key': this.apiKey }
});
return response.data;
}
async createBuild(name, filePath) {
// Create build
const createResponse = await axios.post(
`${this.baseUrl}/builds/create`,
{
name,
file: {
name: path.basename(filePath),
crc32c: this.calculateCRC32C(filePath)
}
},
{
headers: {
'X-Stash-Api-Key': this.apiKey,
'Content-Type': 'application/json'
}
}
);
const { id, signed_resumable_post_url, headers } = createResponse.data;
// Upload file
await this.uploadFile(signed_resumable_post_url, headers, filePath);
// Wait for processing and update
return await this.waitForProcessingAndUpdate(id);
}
async setChannel(channelId, platform, artifactId) {
const response = await axios.post(
`${this.baseUrl}/channels/set`,
{
partner_id: this.partnerId,
shop_id: this.shopId,
channel_id: channelId,
platform,
artifact_id: artifactId
},
{
headers: {
'X-Stash-Api-Key': this.apiKey,
'Content-Type': 'application/json'
}
}
);
return response.data;
}
async saveTokens(accessToken, refreshToken) {
const response = await axios.post(
'https://api.stash.gg/api/v1/sdk/launcher/tokens/save',
{
access_token: accessToken,
refresh_token: refreshToken
},
{
headers: {
'X-Stash-Api-Key': this.apiKey,
'Content-Type': 'application/json'
}
}
);
return response.data.code_challenge;
}
}
// Usage
const launcher = new LauncherService(
process.env.STASH_API_KEY,
'your-partner-id',
'your-shop-id'
);
// Get available channels
const status = await launcher.getStatus();
console.log('Available channels:', status.channels);
// Create and upload a new build
const buildId = await launcher.createBuild('v1.2.3', './build.zip');
// Set build to a channel
await launcher.setChannel('stable', 'WIN_PLATFORM', buildId);Related Documentation
How is this guide?
Integrating Stash Launcher
Learn how to set up Stash Launcher with no coding required. Customize your launcher's appearance, configure release channels, and upload your first build through an intuitive interface in Stash Studio.
Manage Builds, Releases, and Channels
Learn how to organize and distribute your game binaries using Stash Launcher. Understand how to create and update builds, manage channels for different audiences, and control player access with public or restricted configurations.