License Validation API
The License Validation API allows you to programmatically verify license keys. This is useful for custom launchers, build systems, or additional license checks within your game.
Endpoint
POST https://store.gamedna.studio/api/licenses/validateAuthentication
This endpoint does not require authentication headers. The license key itself serves as the credential.
Request
Headers
| Header | Value | Required |
|---|---|---|
Content-Type | application/json | ✅ |
Body
{ "license_key": "GDNA-XXXX-XXXX-XXXX", "machine_id": "unique-machine-identifier", "product_slug": "online-subsystem-blueprintable"}Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
license_key | string | ✅ | The license key to validate (format: GDNA-XXXX-XXXX-XXXX) |
machine_id | string | ✅ | Unique identifier for the machine (see below) |
product_slug | string | ✅ | The product slug to validate against |
Machine ID Generation
The machine ID should be a stable, unique identifier for the device. Recommended approaches:
FString MachineId = FPlatformMisc::GetMachineId().ToString();string machineId = Environment.MachineName + "_" + System.Security.Cryptography.SHA256.Create() .ComputeHash(Encoding.UTF8.GetBytes( Environment.ProcessorCount.ToString() + Environment.OSVersion.ToString() ));# macOSsystem_profiler SPHardwareDataType | grep "Hardware UUID" | awk '{print $3}'
# Linuxcat /etc/machine-id
# Windowswmic csproduct get uuidResponse
Success Response (200 OK)
{ "valid": true, "license": { "id": "lic_abc123", "type": "pro", "seats_used": 3, "seats_total": 5, "owner_email": "user@example.com", "product": { "slug": "online-subsystem-blueprintable", "name": "Online Subsystem Blueprintable", "version": "3.2.0" }, "created_at": "2025-06-15T10:30:00Z", "expires_at": "2027-06-15T10:30:00Z", "is_active": true }, "activation": { "id": "act_xyz789", "machine_id": "unique-machine-identifier", "activated_at": "2026-01-10T14:00:00Z", "last_validated": "2026-01-22T09:30:00Z" }}Error Responses
Invalid License Key (400)
{ "error": { "code": "INVALID_LICENSE_KEY", "message": "The provided license key is not valid.", "details": { "license_key": "Format should be GDNA-XXXX-XXXX-XXXX" } }}License Not Found (404)
{ "error": { "code": "LICENSE_NOT_FOUND", "message": "No license found with the provided key.", "details": null }}License Expired (403)
{ "error": { "code": "LICENSE_EXPIRED", "message": "This license has expired.", "details": { "expired_at": "2025-12-31T23:59:59Z", "renewal_url": "https://store.gamedna.studio/dashboard/licenses/lic_abc123/renew" } }}No Seats Available (403)
{ "error": { "code": "NO_SEATS_AVAILABLE", "message": "All license seats are in use.", "details": { "seats_used": 5, "seats_total": 5, "upgrade_url": "https://store.gamedna.studio/dashboard/licenses/lic_abc123/upgrade" } }}Product Mismatch (400)
{ "error": { "code": "PRODUCT_MISMATCH", "message": "This license is not valid for the specified product.", "details": { "licensed_product": "discord-messenger", "requested_product": "online-subsystem-blueprintable" } }}Rate Limited (429)
{ "error": { "code": "RATE_LIMITED", "message": "Too many validation requests. Please try again later.", "details": { "retry_after": 60 } }}Error Codes
| Code | HTTP Status | Description |
|---|---|---|
INVALID_LICENSE_KEY | 400 | License key format is invalid |
MISSING_REQUIRED_FIELD | 400 | Required field missing from request |
PRODUCT_MISMATCH | 400 | License doesn’t cover requested product |
LICENSE_EXPIRED | 403 | License has expired |
LICENSE_SUSPENDED | 403 | License has been suspended |
NO_SEATS_AVAILABLE | 403 | All seats are in use |
LICENSE_NOT_FOUND | 404 | License key doesn’t exist |
RATE_LIMITED | 429 | Too many requests |
INTERNAL_ERROR | 500 | Server error |
Examples
cURL
curl -X POST https://store.gamedna.studio/api/licenses/validate \ -H "Content-Type: application/json" \ -d '{ "license_key": "GDNA-ABCD-1234-EFGH", "machine_id": "my-dev-machine-001", "product_slug": "online-subsystem-blueprintable" }'JavaScript/TypeScript
async function validateLicense( licenseKey: string, machineId: string, productSlug: string): Promise<LicenseValidation> { const response = await fetch( 'https://store.gamedna.studio/api/licenses/validate', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ license_key: licenseKey, machine_id: machineId, product_slug: productSlug, }), } );
if (!response.ok) { const error = await response.json(); throw new Error(error.error.message); }
return response.json();}
// Usagetry { const result = await validateLicense( 'GDNA-ABCD-1234-EFGH', getMachineId(), 'online-subsystem-blueprintable' );
if (result.valid) { console.log(`License valid! Type: ${result.license.type}`); }} catch (error) { console.error('License validation failed:', error.message);}C++ (Unreal Engine)
void ULicenseManager::ValidateLicense( const FString& LicenseKey, const FString& ProductSlug){ FString MachineId = FPlatformMisc::GetMachineId().ToString();
TSharedRef<IHttpRequest> Request = FHttpModule::Get().CreateRequest(); Request->SetURL(TEXT("https://store.gamedna.studio/api/licenses/validate")); Request->SetVerb(TEXT("POST")); Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject); JsonObject->SetStringField(TEXT("license_key"), LicenseKey); JsonObject->SetStringField(TEXT("machine_id"), MachineId); JsonObject->SetStringField(TEXT("product_slug"), ProductSlug);
FString RequestBody; TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&RequestBody); FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer);
Request->SetContentAsString(RequestBody); Request->OnProcessRequestComplete().BindUObject( this, &ULicenseManager::OnValidationResponse); Request->ProcessRequest();}
void ULicenseManager::OnValidationResponse( FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess){ if (!bSuccess || !Response.IsValid()) { OnLicenseValidationFailed.Broadcast(TEXT("Network error")); return; }
TSharedPtr<FJsonObject> JsonResponse; TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create( Response->GetContentAsString());
if (FJsonSerializer::Deserialize(Reader, JsonResponse)) { bool bValid = JsonResponse->GetBoolField(TEXT("valid")); if (bValid) { OnLicenseValidationSuccess.Broadcast(); } else { FString ErrorMessage = JsonResponse ->GetObjectField(TEXT("error")) ->GetStringField(TEXT("message")); OnLicenseValidationFailed.Broadcast(ErrorMessage); } }}Rate Limits
| Endpoint | Limit | Window |
|---|---|---|
/api/licenses/validate | 60 requests | 1 minute |
Best Practices
- Cache Results - Don’t validate on every frame. Cache for the session.
- Handle Offline - Have graceful fallback when network is unavailable.
- Secure Machine ID - Don’t expose the machine ID generation method.
- Log Failures - Log validation failures for support purposes.
- User Feedback - Show clear messages when validation fails.
Related
- License Activation API - Activate new licenses
- Downloads API - Generate download tokens
- Licensing Guide - Understanding license types