Command Center API — Product Requirements Document
Version: 1.0
Date: July 2026
Author: Pete Carroll, Founder — Traxs Group LLC
Status: Draft
1. Overview
This document defines the detailed requirements for each Command Center API endpoint — what it accepts, what it returns, how it behaves, and how errors are handled.
2. Authentication Model
The Command Center API has two authentication tiers:
2.1 Public Endpoints (Unauthenticated)
Used by browser-facing forms on marketing and company sites. No API key required but CORS restricts to Traxs domains.
Public endpoints:
POST /v1/contactPOST /v1/supportPOST /v1/feedbackGET /health
Rate limiting: 10 requests per minute per IP address.
CORS allowed origins:
[
"https://roundtrips.app",
"https://www.roundtrips.app",
"https://traxsgroup.com",
"https://www.traxsgroup.com",
"https://roundtrip-marketing.pages.dev"
]
2.2 Service-to-Service Endpoints (API Key)
Used by Waypoint API, Relay API, and future products. Requires X-CommandCenter-ApiKey header.
Service endpoints:
- All
/v1/roundtrip/*endpoints - All
/v1/suite/*endpoints - All
/v1/clients/*endpoints - All
/v1/relay/*endpoints (future)
API key storage: CommandCenter--ApiKey in each consuming product's Key Vault. The Command Center API validates against its own stored key.
2.3 Authentication User Stories
FA01: As a user I expect to be able to log into the system through Keystone and the system knows what subscription I am on and what products I have subscribed to.
USA01: Authentication workflow retrieves users subscription by TenantId
USA02: Authentication workflow retrieves users product entitlements by TanantId
3. Endpoint Specifications
3.1 Health Check
GET /health
Public. No auth required.
Response 200:
{ }
3.2 Contact Form
POST /v1/contact
Public. Unauthenticated. Rate limited.
Request body:
{
"firstName": "Pete",
"lastName": "Carroll",
"email": "pete@example.com",
"company": "Carroll HVAC",
"message": "I'd like to see a demo.",
"type": 0
}
ContactRequestType enum:
| Value | Name | Description |
|---|---|---|
| 0 | GeneralInquiry | General question |
| 1 | Support | Support request |
| 2 | Feedback | Product feedback |
| 3 | DemoRequest | Demo booking request |
Validation:
firstName— required, max 100 charslastName— required, max 100 charsemail— required, valid email format, max 255 charsmessage— required, max 5000 charscompany— optional, max 200 charstype— required, valid enum value
On success:
- Send confirmation email to submitter via SendGrid
- From:
noreply@roundtrips.app - From name:
RoundTrip by Traxs Group LLC - Subject:
We received your message — RoundTrip by Traxs Group LLC
- From:
- Send notification email to
hello@traxsgroup.com- Subject:
[{type}] New contact from {firstName} {lastName} - Body: full submission details
- Subject:
Response 200:
{
"success": true,
"errorMessage": null
}
Response 422 (validation failure):
{
"success": false,
"errorMessage": "Email address is required."
}
Response 500 (SendGrid failure):
{
"success": false,
"errorMessage": "Failed to send email."
}
3.3 Support Request
POST /v1/support
Public. Unauthenticated. Rate limited.
Request body:
{
"firstName": "Mike",
"lastName": "Callahan",
"email": "mike@hvacpros.com",
"company": "HVAC Pros LLC",
"subject": "Invoice not sending",
"message": "When I click Send Invoice nothing happens.",
"product": "RoundTrip",
"priority": "Normal"
}
Validation: Same as contact form plus:
subject— required, max 200 charsproduct— required, one of:RoundTrip,Waypoint,Relay,Generalpriority— optional, one of:Low,Normal,High,Critical
On success:
- Send confirmation email to submitter
- Send notification email to
support@roundtrips.app - Future: Create Crisp ticket via Crisp API
Response: Same pattern as /v1/contact
3.4 In-App Feedback
POST /v1/feedback
Public. Rate limited. Called from within Traxs product apps.
Request body:
{
"email": "mike@hvacpros.com",
"name": "Mike Callahan",
"product": "RoundTrip",
"category": "FeatureRequest",
"message": "It would be great if I could schedule recurring jobs.",
"currentPage": "/tickets"
}
On success:
- Send notification to
hello@traxsgroup.com - Future: Create Linear issue via Linear API
Response: Same pattern as /v1/contact
3.5 RoundTrip Revenue Summary
GET /v1/roundtrip/revenue-summary
Service-to-service. Requires X-CommandCenter-ApiKey header.
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
tenantId | Guid | Yes | The Traxs tenant ID |
period | string | No | mtd (default), ytd, last30, last90 |
How it works:
- Command Center API receives request from Waypoint
- Validates API key
- Calls RoundTrip API:
GET /v1/internal/revenue-summary?tenantId={id}&period={period} - RoundTrip API validates that the tenantId exists and has an active subscription
- Returns revenue data
- Command Center API translates to its own DTO and returns to Waypoint
Response 200:
{
"tenantId": "ad05b136-e770-4650-b563-75b7dad17234",
"period": "mtd",
"totalRevenue": 12480.00,
"currency": "USD",
"paidInvoiceCount": 14,
"fromDate": "2026-07-01",
"toDate": "2026-07-07",
"asOf": "2026-07-07T20:30:00Z"
}
Response 404: Tenant not found or not subscribed to RoundTrip
Response 503: RoundTrip API unavailable — Waypoint handles gracefully
3.6 RoundTrip Open Tickets
GET /v1/roundtrip/tickets/open
Service-to-service.
Query parameters:
| Parameter | Type | Required |
|---|---|---|
tenantId | Guid | Yes |
Response 200:
{
"tenantId": "...",
"totalOpen": 8,
"unassigned": 3,
"inProgress": 2,
"assigned": 3,
"estimatedValue": 3600.00,
"currency": "USD",
"asOf": "2026-07-07T20:30:00Z"
}
3.7 RoundTrip Recent Invoices
GET /v1/roundtrip/invoices/recent
Service-to-service.
Query parameters:
| Parameter | Type | Required | Default |
|---|---|---|---|
tenantId | Guid | Yes | — |
count | int | No | 5 |
Response 200:
{
"tenantId": "...",
"invoices": [
{
"invoiceNumber": "INV-2026-0042",
"clientName": "Briggs Property Management",
"amount": 850.00,
"currency": "USD",
"status": "Paid",
"sentAt": "2026-07-05T14:30:00Z"
}
],
"asOf": "2026-07-07T20:30:00Z"
}
3.8 RoundTrip Team
GET /v1/roundtrip/team
Service-to-service.
Query parameters:
| Parameter | Type | Required |
|---|---|---|
tenantId | Guid | Yes |
Response 200:
{
"tenantId": "...",
"members": [
{
"userId": "...",
"firstName": "Derek",
"lastName": "Osei",
"role": "Technician",
"isActive": true
}
],
"asOf": "2026-07-07T20:30:00Z"
}
3.9 RoundTrip AR Aging
GET /v1/roundtrip/ar/aging
Service-to-service.
Response 200:
{
"tenantId": "...",
"totalOutstanding": 4200.00,
"currency": "USD",
"buckets": {
"current": { "amount": 1800.00, "count": 3 },
"thirtyDays": { "amount": 1200.00, "count": 2 },
"sixtyDays": { "amount": 800.00, "count": 1 },
"ninetyPlus": { "amount": 400.00, "count": 1 }
},
"oldestInvoiceDays": 87,
"asOf": "2026-07-07T20:30:00Z"
}
3.10 Suite Subscriptions
GET /v1/suite/subscriptions
Service-to-service. Called by product frontends to determine suite switcher display.
Query parameters:
| Parameter | Type | Required |
|---|---|---|
tenantId | Guid | Yes |
Response 200:
{
"tenantId": "...",
"subscriptions": {
"roundtrip": {
"active": true,
"plan": "Standard",
"url": "https://app.roundtrips.app"
},
"waypoint": {
"active": true,
"plan": "Starter",
"url": "https://traxswaypoint.app"
},
"relay": {
"active": false,
"plan": null,
"url": "https://traxsrelay.app"
}
}
}
Implementation: Queries Stripe for active subscriptions by customer ID. Stripe customer is shared across products — identified by TenantId metadata on the Stripe customer record.
4. Error Handling
All endpoints return consistent error responses:
{
"success": false,
"errorMessage": "Human-readable error message",
"errorCode": "OPTIONAL_CODE"
}
HTTP status codes:
| Code | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad request (malformed JSON) |
| 401 | Missing or invalid API key (service endpoints) |
| 422 | Validation failure |
| 429 | Rate limit exceeded |
| 500 | Internal error |
| 503 | Upstream service (RoundTrip, SendGrid) unavailable |
5. Versioning
All endpoints are versioned via URL prefix (/v1/). Breaking changes require a new version (/v2/). The v1 contract is stable — no breaking changes to existing v1 endpoints.
Adding new endpoints to v1 is not a breaking change.
6. Document History
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | July 2026 | Pete Carroll | Initial draft |