Skip to main content

Command Center API — Solution Architecture Document

Version: 1.0
Date: July 2026
Author: Pete Carroll, Founder — Traxs Group LLC
Status: Draft


1. Architecture Overview

The Command Center API is a lightweight .NET 10 API built with the same Clean Architecture as RoundTrip and Waypoint. It has no database in v1 — it is a pure pass-through and aggregation layer between products and external services.


2. System Context

┌─────────────────────────────────────────────────────────────────────┐
│ COMMAND CENTER API CONSUMERS │
│ │
│ Browser Forms Waypoint API Relay API (future) │
│ (roundtrips.app (service-to- (service-to- │
│ traxsgroup.com) service) service) │
└──────────┬──────────────────┬────────────────────┬──────────────────┘
│ HTTPS (public) │ HTTPS + API Key │
▼ ▼ ▼
┌──────────────────────────────────────────────────────────────────────┐
│ COMMAND CENTER API │
│ api.traxsgroup.com │
│ Azure App Service — app-commandcenter-production │
│ .NET 10 · FastEndpoints · Cyrus Mediator │
│ No database (v1) │
└──────────┬───────────────────────┬──────────────────────────────────┘
│ │
┌──────▼──────┐ ┌───────────▼──────────────────────────┐
│ SENDGRID │ │ ROUNDTRIP API │
│ Email │ │ api.roundtrips.app │
│ delivery │ │ (service-to-service, internal token) │
└─────────────┘ └───────────────────────────────────────┘

3. Solution Structure

CommandCenterAPI/
CommandCenter.API.Core/
Interfaces/
IEmailService.cs
IRoundTripClient.cs ← new
ISuiteSubscriptionService.cs ← new
Models/
EmailMessage.cs
ContactRequest.cs
RoundTripRevenueSummary.cs ← new
RoundTripTicketSummary.cs ← new
SuiteSubscriptions.cs ← new
Enums/
ContactRequestType.cs

CommandCenter.API.UseCases/
Features/
Contact/
SubmitContactCommand.cs
SubmitContactHandler.cs
SubmitContactValidator.cs
Support/ ← new
SubmitSupportCommand.cs
SubmitSupportHandler.cs
Feedback/ ← new
SubmitFeedbackCommand.cs
SubmitFeedbackHandler.cs
RoundTrip/ ← new
GetRevenueSummaryQuery.cs
GetRevenueSummaryHandler.cs
GetOpenTicketsQuery.cs
GetOpenTicketsHandler.cs
GetRecentInvoicesQuery.cs
GetRecentInvoicesHandler.cs
GetTeamQuery.cs
GetTeamHandler.cs
GetARAgingQuery.cs
GetARAgingHandler.cs
Suite/ ← new
GetSubscriptionsQuery.cs
GetSubscriptionsHandler.cs

CommandCenter.API.Infrastructure/
Services/
SendGridEmailService.cs
RoundTripClient.cs ← new — calls RoundTrip API
StripeSubscriptionService.cs ← new — queries Stripe for suite subscriptions
Extensions/
InfrastructureServiceExtensions.cs

CommandCenter.API.Web/
Endpoints/
Contact/
SubmitContactEndpoint.cs
Support/ ← new
SubmitSupportEndpoint.cs
Feedback/ ← new
SubmitFeedbackEndpoint.cs
RoundTrip/ ← new
GetRevenueSummaryEndpoint.cs
GetOpenTicketsEndpoint.cs
GetRecentInvoicesEndpoint.cs
GetTeamEndpoint.cs
GetARAgingEndpoint.cs
Suite/ ← new
GetSubscriptionsEndpoint.cs
HealthEndpoint.cs
Middleware/
ApiKeyAuthMiddleware.cs ← new — validates X-CommandCenter-ApiKey
Program.cs

4. Authentication Implementation

4.1 Public Endpoint Authentication

Public endpoints (/v1/contact, /v1/support, /v1/feedback, /health) use AllowAnonymous() in FastEndpoints. CORS policy restricts to Traxs domains.

Rate limiting via ASP.NET Core rate limiting middleware:

// Program.cs
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("public", limiter =>
{
limiter.PermitLimit = 10;
limiter.Window = TimeSpan.FromMinutes(1);
limiter.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
limiter.QueueLimit = 0;
});
});

4.2 Service-to-Service Authentication

Service endpoints validate the X-CommandCenter-ApiKey header via a custom middleware:

public sealed class ApiKeyAuthMiddleware(
IConfiguration configuration,
RequestDelegate next)
{
private const string ApiKeyHeader = "X-CommandCenter-ApiKey";

public async Task InvokeAsync(HttpContext context)
{
// Skip auth for public endpoints
var endpoint = context.GetEndpoint();
if (endpoint?.Metadata.GetMetadata<AllowAnonymousAttribute>() != null)
{
await next(context);
return;
}

if (!context.Request.Headers.TryGetValue(
ApiKeyHeader, out var extractedApiKey))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsJsonAsync(
new { success = false, errorMessage = "API key required." });
return;
}

var apiKey = configuration["CommandCenter:ApiKey"];
if (!apiKey.Equals(extractedApiKey))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsJsonAsync(
new { success = false, errorMessage = "Invalid API key." });
return;
}

await next(context);
}
}

5. RoundTrip Client Implementation

The RoundTripClient calls the RoundTrip API using service-to-service auth. RoundTrip needs to expose internal endpoints for this purpose.

public sealed class RoundTripClient : IRoundTripClient
{
private readonly HttpClient _http;
private readonly ILogger<RoundTripClient> _logger;

public RoundTripClient(
HttpClient http,
ILogger<RoundTripClient> logger)
{
_http = http;
_logger = logger;
}

public async Task<RoundTripRevenueSummary?> GetRevenueSummaryAsync(
Guid tenantId,
string period,
CancellationToken ct)
{
try
{
var response = await _http.GetAsync(
$"/v1/internal/revenue-summary?tenantId={tenantId}&period={period}",
ct);

if (response.StatusCode == HttpStatusCode.NotFound)
return null;

response.EnsureSuccessStatusCode();

return await response.Content
.ReadFromJsonAsync<RoundTripRevenueSummary>(ct);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Failed to get revenue summary from RoundTrip for tenant {TenantId}",
tenantId);
return null; // Graceful degradation — Waypoint handles null
}
}
}

Registered with HttpClientFactory in DI:

builder.Services.AddHttpClient<IRoundTripClient, RoundTripClient>(client =>
{
client.BaseAddress = new Uri(
configuration["RoundTripApi:BaseUrl"]
?? throw new InvalidOperationException(
"RoundTripApi:BaseUrl is not configured."));
client.DefaultRequestHeaders.Add(
"X-Internal-ApiKey",
configuration["RoundTripApi:InternalApiKey"]
?? throw new InvalidOperationException(
"RoundTripApi:InternalApiKey is not configured."));
client.Timeout = TimeSpan.FromSeconds(10);
});

6. RoundTrip Internal Endpoints (New endpoints needed in RoundTrip API)

The Command Center API requires RoundTrip to expose these internal endpoints. These are service-to-service only — not accessible from the browser.

EndpointWhat It Returns
GET /v1/internal/revenue-summaryMTD/YTD revenue from paid invoices
GET /v1/internal/tickets/openOpen ticket count, status breakdown, estimated value
GET /v1/internal/invoices/recentLast N invoices with status
GET /v1/internal/teamActive TenantUsers with roles
GET /v1/internal/ar/agingOutstanding invoices by age bucket

All internal endpoints:

  • Require X-Internal-ApiKey header
  • Include tenantId as query parameter
  • Return 404 if tenant not found
  • Return data scoped to the specified tenant

These will be added as Linear tickets under TRA-NNN when Waypoint M7 is reached.


7. Infrastructure

7.1 Azure Resources (Current)

ResourceName
Resource Grouprg-commandcenter-production
App Serviceapp-commandcenter-production
Key Vaultkv-commandcenter-pro
Custom Domainapi.traxsgroup.com

7.2 Key Vault Secrets (Current + Planned)

SecretPurposeStatus
SendGrid--ApiKeyEmail delivery✅ Live
CommandCenter--ApiKeyInbound service-to-service auth⏳ Add for Waypoint
RoundTripApi--BaseUrlRoundTrip API URL⏳ Add for Waypoint
RoundTripApi--InternalApiKeyOutbound auth to RoundTrip⏳ Add for Waypoint
Stripe--SecretKeySuite subscription queries⏳ Add for Waypoint

7.3 CORS Configuration

Update appsettings.json when Waypoint launches:

{
"Cors": {
"AllowedOrigins": [
"https://roundtrips.app",
"https://www.roundtrips.app",
"https://traxsgroup.com",
"https://www.traxsgroup.com",
"https://roundtrip-marketing.pages.dev",
"https://traxswaypoint.app",
"https://dev.traxswaypoint.app"
]
}
}

Note: Service-to-service calls from Waypoint API are server-to-server — they don't go through the browser so CORS doesn't apply to them. CORS only matters for browser-facing public endpoints.


8. Graceful Degradation

The Command Center API must never become a single point of failure. If it is unavailable:

  • Contact forms: show "Unable to send — please email us directly at hello@traxsgroup.com"
  • Waypoint dashboard: RT-dependent widgets show "RoundTrip data temporarily unavailable — check back shortly"
  • Suite switcher: hides products that can't be confirmed (conservative — show fewer options)

Products must implement timeouts on all Command Center API calls (10 seconds max) and handle null/error responses gracefully.


9. Growth Path

The Command Center API grows incrementally as products are added:

Current (July 2026):
Contact form → SendGrid

Waypoint launch (Q1 2027):
+ RoundTrip data endpoints (5 endpoints)
+ Suite subscriptions endpoint
+ Support/feedback endpoints
+ API key authentication

Relay launch (Q3 2027):
+ Lead sync endpoint (Relay → RoundTrip)
+ Client sync endpoint (bidirectional)
+ Campaign trigger events

2028+:
+ Unified billing hub (Stripe orchestration)
+ AI inference gateway (Anthropic API routing)
+ Webhook management (event bus)
+ Platform analytics

10. Document History

VersionDateAuthorChanges
1.0July 2026Pete CarrollInitial draft