Integration Guide
Guide for application developers integrating with the Riptide Application Manager API.
Table of Contents
- Overview
- Authentication
- SDK Libraries
- Common Integration Patterns
- Trial User Management
- Configuration Management
- Session Management
- Error Handling
- Best Practices
- Code Examples
Overview
The Riptide Application Manager provides:
- Identity Management: Trial users, applications, sessions, roles
- Configuration Management: Application settings, file versioning, rollbacks
Base URLs
- API:
http://localhost:11402(development) orhttps://api.your-domain.com(production) - Web UI:
http://localhost:11401(development) orhttps://app.your-domain.com(production)
Note: Application Manager listens on HTTP. In production, place it behind a reverse proxy (NGINX, Caddy, Azure App Gateway, etc.) that terminates TLS and forwards traffic over the internal network. This keeps certificate management in one place and ensures all client traffic is encrypted in transit.
API Documentation
Full API reference available at api-reference.md
Authentication
API Key
All API requests require an API key passed in the X-Api-Key header:
curl -X GET http://localhost:11402/api/users \
-H "X-Api-Key: rtk_your-api-key"
API keys are issued and managed through the Application Manager Web UI under each application's API Keys page. Each key has a unique prefix (e.g. rtk_abc123) for identification. The raw key value is displayed once at creation and cannot be retrieved again — store it securely.
JWT Authentication
The SDK's Identity component issues RS256-signed JWT access tokens and refresh tokens. API requests can authenticate with a Bearer token in the Authorization header:
curl -X GET http://localhost:11402/api/users \
-H "Authorization: Bearer eyJhbG..."
Access tokens contain the user's identity, email, and any custom claims. Token lifetimes are configurable via AccessTokenExpirationMinutes and RefreshTokenExpirationDays in the identity configuration. Refresh tokens are cryptographically random and can be exchanged for new access tokens without re-authentication.
In production environments, the service requires explicit RSA key configuration — auto-generated keys are not permitted.
SDK Libraries
.NET SDK (Recommended)
using Riptide.ApplicationManager.Client;
// Initialize client
var client = new ApplicationManagerClient(new ApplicationManagerOptions
{
BaseUrl = "http://localhost:11402",
ApiKey = "rtk_your-api-key"
});
// Get trial user
var user = await client.TrialUsers.GetByEmailAsync("user@example.com");
// Get configuration
var config = await client.Configuration.GetFileAsync("my-app", "appsettings.json");
HTTP Clients (Any Language)
For languages without an SDK, use standard HTTP libraries:
Python:
import requests
headers = {
"X-Api-Key": "rtk_your-api-key",
"Content-Type": "application/json"
}
response = requests.get(
"http://localhost:11402/api/users",
headers=headers
)
JavaScript/Node.js:
const axios = require('axios');
const client = axios.create({
baseURL: 'http://localhost:11402',
headers: {
'X-Api-Key': 'rtk_your-api-key'
}
});
const users = await client.get('/api/users');
Common Integration Patterns
1. Trial User Validation
Check if a user has an active trial before granting access:
public async Task<bool> ValidateTrialAccessAsync(string email)
{
var user = await _appManagerClient.TrialUsers.GetByEmailAsync(email);
if (user == null || !user.IsActive)
return false;
if (user.Trial.Status != TrialStatus.Active)
return false;
if (user.Trial.EndDate < DateTime.UtcNow)
return false;
return true;
}
2. Configuration Loading
Load application configuration on startup:
public async Task<AppConfiguration> LoadConfigurationAsync(string appName)
{
var configFile = await _appManagerClient.Configuration
.GetFileAsync(appName, "appsettings.json");
return JsonSerializer.Deserialize<AppConfiguration>(configFile.Content);
}
3. Session Tracking
Track user sessions for analytics and security:
public async Task<Session> StartUserSessionAsync(int userId, string ipAddress)
{
var session = await _appManagerClient.Sessions.CreateAsync(new CreateSessionRequest
{
TrialUserId = userId,
IpAddress = ipAddress,
UserAgent = GetUserAgent(),
SessionStartedAt = DateTime.UtcNow
});
return session;
}
public async Task EndUserSessionAsync(int sessionId)
{
await _appManagerClient.Sessions.EndAsync(sessionId);
}
Trial User Management
Check Trial Status
GET /api/trial-users/{id}
X-Api-Key: rtk_your-api-key
Response:
{
"id": 1,
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe",
"isActive": true,
"trial": {
"id": 1,
"status": "Active",
"startDate": "2026-01-20T00:00:00Z",
"endDate": "2026-01-27T00:00:00Z",
"daysRemaining": 0,
"isExpired": false
}
}
Create Trial User
POST /api/trial-users
Content-Type: application/json
X-Api-Key: rtk_your-api-key
{
"email": "newuser@example.com",
"firstName": "Jane",
"lastName": "Smith",
"companyName": "Acme Corp",
"applicationId": 1
}
Extend Trial
PATCH /api/trials/{id}/extend
Content-Type: application/json
X-Api-Key: rtk_your-api-key
{
"additionalDays": 7
}
Configuration Management
Get Configuration File
GET /api/configuration/{appName}/files/{fileName}
X-Api-Key: rtk_your-api-key
Response:
{
"id": 1,
"fileName": "appsettings.json",
"content": "{ \"key\": \"value\" }",
"version": 3,
"createdAt": "2026-01-27T10:00:00Z"
}
Update Configuration
PUT /api/configuration/{appName}/files/{fileName}
Content-Type: application/json
X-Api-Key: rtk_your-api-key
{
"content": "{ \"key\": \"new-value\" }"
}
Rollback Configuration
POST /api/configuration/{appName}/files/{fileName}/rollback/{version}
X-Api-Key: rtk_your-api-key
Session Management
Create Session
POST /api/sessions
Content-Type: application/json
X-Api-Key: rtk_your-api-key
{
"trialUserId": 1,
"ipAddress": "192.168.1.100",
"userAgent": "Mozilla/5.0...",
"sessionStartedAt": "2026-01-27T10:00:00Z"
}
End Session
DELETE /api/sessions/{id}
X-Api-Key: rtk_your-api-key
Error Handling
Standard Error Response
The API uses RFC 7807 Problem Details for error responses:
{
"type": "https://tools.ietf.org/html/rfc7807#section-3.1",
"title": "Validation Error",
"status": 400,
"detail": "Email is required",
"traceId": "00-abc123..."
}
Common Error Codes
VALIDATION_ERROR(400): Invalid request dataUNAUTHORIZED(401): Missing or invalid API keyNOT_FOUND(404): Resource not foundCONFLICT(409): Resource already existsINTERNAL_ERROR(500): Server error
Error Handling Example
try
{
var user = await client.TrialUsers.GetByEmailAsync(email);
}
catch (ApplicationManagerException ex) when (ex.StatusCode == 404)
{
// User not found - create new trial
var newUser = await client.TrialUsers.CreateAsync(request);
}
catch (ApplicationManagerException ex) when (ex.StatusCode == 401)
{
// Authentication failed - check API key
_logger.LogError("Invalid API key");
throw;
}
catch (ApplicationManagerException ex)
{
// Other API error
_logger.LogError(ex, "API error: {Code}", ex.ErrorCode);
throw;
}
Best Practices
1. Cache Configuration
Don't fetch configuration on every request:
private IMemoryCache _cache;
private TimeSpan _cacheDuration = TimeSpan.FromMinutes(5);
public async Task<AppConfiguration> GetConfigurationAsync(string appName)
{
return await _cache.GetOrCreateAsync($"config:{appName}", async entry =>
{
entry.AbsoluteExpirationRelativeToNow = _cacheDuration;
return await LoadConfigurationFromApiAsync(appName);
});
}
2. Validate Trial Status Periodically
Check trial status at application startup and periodically during runtime:
// On startup
var isValid = await ValidateTrialAccessAsync(userEmail);
if (!isValid)
{
throw new TrialExpiredException("Your trial has expired");
}
// Background validation every hour
var timer = new Timer(async _ =>
{
await ValidateTrialAccessAsync(userEmail);
}, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1));
3. Handle Network Failures Gracefully
public async Task<T> CallApiWithRetryAsync<T>(Func<Task<T>> apiCall)
{
var retryPolicy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
return await retryPolicy.ExecuteAsync(apiCall);
}
4. Log API Interactions
_logger.LogInformation("Validating trial access for {Email}", email);
var user = await client.TrialUsers.GetByEmailAsync(email);
_logger.LogInformation("Trial status: {Status}, Days remaining: {Days}",
user.Trial.Status, user.Trial.DaysRemaining);
5. Use Environment Variables for Configuration
Never hardcode secrets:
var options = new ApplicationManagerOptions
{
BaseUrl = Environment.GetEnvironmentVariable("APP_MANAGER_URL")
?? "http://localhost:11402",
ApiKey = Environment.GetEnvironmentVariable("APP_MANAGER_API_KEY")
?? throw new InvalidOperationException("APP_MANAGER_API_KEY not set")
};
Code Examples
Complete Integration Example (.NET)
using Microsoft.Extensions.DependencyInjection;
using Riptide.ApplicationManager.Client;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Register Application Manager client
services.AddSingleton<IApplicationManagerClient>(sp =>
{
var options = new ApplicationManagerOptions
{
BaseUrl = Configuration["ApplicationManager:BaseUrl"],
ApiKey = Configuration["ApplicationManager:ApiKey"]
};
return new ApplicationManagerClient(options);
});
// Register your services
services.AddScoped<ITrialValidator, TrialValidator>();
}
}
public interface ITrialValidator
{
Task<TrialValidationResult> ValidateAsync(string email);
}
public class TrialValidator : ITrialValidator
{
private readonly IApplicationManagerClient _client;
private readonly ILogger<TrialValidator> _logger;
public TrialValidator(
IApplicationManagerClient client,
ILogger<TrialValidator> logger)
{
_client = client;
_logger = logger;
}
public async Task<TrialValidationResult> ValidateAsync(string email)
{
try
{
var user = await _client.TrialUsers.GetByEmailAsync(email);
if (user == null)
{
return TrialValidationResult.NotFound();
}
if (!user.IsActive)
{
return TrialValidationResult.Inactive();
}
if (user.Trial.IsExpired)
{
return TrialValidationResult.Expired(user.Trial.EndDate);
}
return TrialValidationResult.Valid(user, user.Trial.DaysRemaining);
}
catch (ApplicationManagerException ex)
{
_logger.LogError(ex, "Error validating trial for {Email}", email);
return TrialValidationResult.Error(ex.Message);
}
}
}
public class TrialValidationResult
{
public bool IsValid { get; init; }
public string Message { get; init; }
public int? DaysRemaining { get; init; }
public static TrialValidationResult Valid(TrialUser user, int daysRemaining) =>
new() { IsValid = true, Message = "Trial is active", DaysRemaining = daysRemaining };
public static TrialValidationResult NotFound() =>
new() { IsValid = false, Message = "User not found" };
public static TrialValidationResult Inactive() =>
new() { IsValid = false, Message = "User account is inactive" };
public static TrialValidationResult Expired(DateTime endDate) =>
new() { IsValid = false, Message = $"Trial expired on {endDate:yyyy-MM-dd}" };
public static TrialValidationResult Error(string message) =>
new() { IsValid = false, Message = $"Validation error: {message}" };
}
Python Integration Example
import requests
from datetime import datetime
from typing import Optional, Dict, Any
class ApplicationManagerClient:
def __init__(self, base_url: str, api_key: str):
self.base_url = base_url
self.headers = {
'X-Api-Key': api_key,
'Content-Type': 'application/json'
}
def get_trial_user(self, email: str) -> Optional[Dict[str, Any]]:
"""Get trial user by email"""
response = requests.get(
f'{self.base_url}/api/trial-users/by-email/{email}',
headers=self.headers
)
if response.status_code == 404:
return None
response.raise_for_status()
return response.json()
def validate_trial(self, email: str) -> Dict[str, Any]:
"""Validate trial access"""
user = self.get_trial_user(email)
if not user:
return {
'valid': False,
'message': 'User not found'
}
if not user['isActive']:
return {
'valid': False,
'message': 'User account is inactive'
}
trial = user['trial']
if trial['isExpired']:
return {
'valid': False,
'message': f"Trial expired on {trial['endDate']}"
}
return {
'valid': True,
'message': 'Trial is active',
'days_remaining': trial['daysRemaining']
}
def get_configuration(self, app_name: str, file_name: str) -> str:
"""Get configuration file content"""
response = requests.get(
f'{self.base_url}/api/configuration/{app_name}/files/{file_name}',
headers=self.headers
)
response.raise_for_status()
return response.json()['content']
# Usage
client = ApplicationManagerClient(
base_url='http://localhost:11402',
api_key='rtk_your-api-key'
)
# Validate trial
result = client.validate_trial('user@example.com')
if result['valid']:
print(f"Access granted. {result['days_remaining']} days remaining.")
else:
print(f"Access denied: {result['message']}")
# Load configuration
config_json = client.get_configuration('my-app', 'appsettings.json')
JavaScript/Node.js Integration Example
const axios = require('axios');
class ApplicationManagerClient {
constructor(baseUrl, apiKey) {
this.client = axios.create({
baseURL: baseUrl,
headers: {
'X-Api-Key': apiKey,
'Content-Type': 'application/json'
}
});
}
async getTrialUser(email) {
try {
const response = await this.client.get(`/api/trial-users/by-email/${email}`);
return response.data;
} catch (error) {
if (error.response?.status === 404) {
return null;
}
throw error;
}
}
async validateTrial(email) {
const user = await this.getTrialUser(email);
if (!user) {
return { valid: false, message: 'User not found' };
}
if (!user.isActive) {
return { valid: false, message: 'User account is inactive' };
}
if (user.trial.isExpired) {
return {
valid: false,
message: `Trial expired on ${user.trial.endDate}`
};
}
return {
valid: true,
message: 'Trial is active',
daysRemaining: user.trial.daysRemaining
};
}
async getConfiguration(appName, fileName) {
const response = await this.client.get(
`/api/configuration/${appName}/files/${fileName}`
);
return response.data.content;
}
}
// Usage
const client = new ApplicationManagerClient(
'http://localhost:11402',
'rtk_your-api-key'
);
// Validate trial
const result = await client.validateTrial('user@example.com');
if (result.valid) {
console.log(`Access granted. ${result.daysRemaining} days remaining.`);
} else {
console.log(`Access denied: ${result.message}`);
}
// Load configuration
const configJson = await client.getConfiguration('my-app', 'appsettings.json');
Related Documentation
- API Reference: Complete API endpoint documentation
- Deployment Guide: Server deployment guide
- Administration Guide: System administration guide
- Configuration Reference: All configuration options
Support
For integration support:
- Check the API Reference for endpoint details
- Review code examples in this guide
- Contact: support@riptide.solutions