Service Composition and Hierarchy API
Audience: Developers, Integration Engineers Last Updated: 2026-01-29 Phase: 41-D-01 (Service Hierarchy Documentation)
Overview
The Service Composition API enables programmatic management of hierarchical service structures, parent/child relationships, and eligibility constraints. This guide provides complete API specifications, code examples, and integration patterns for building service activation flows.
API Capabilities:
- Retrieve service hierarchies (recursive, multi-level)
- Validate service eligibility (REQUIRES, EXCLUDES constraints)
- Activate parent services with automatic child activation (MANDATORY children)
- Manage lifecycle inheritance (terminate parent → children cascade)
- Query service roles, inclusion types, and sort orders
Base URL: https://api.staqr.io/v1
Authentication: Bearer token (see Authentication Guide)
Quick Start
Activate Mobile Service with Hierarchy
import { StaqrClient } from '@staqr/sdk';
const client = new StaqrClient({ apiKey: process.env.STAQR_API_KEY });
// Activate mobile service (automatically activates MANDATORY children)
const result = await client.services.activate({
customerId: 'cust_123',
serviceCode: 'SVC_MOBILE_BASE',
optionalChildren: [
{ code: 'SVC_VOICEMAIL' },
{ code: 'SVC_INTL_ROAMING' }
]
});
console.log(result);
// {
// serviceId: 'svc_inst_456',
// status: 'ACTIVE',
// children: [
// { code: 'SVC_MSN_STANDARD', status: 'ACTIVE' }, // MANDATORY
// { code: 'SVC_SIM_PHYSICAL', status: 'ACTIVE' }, // MANDATORY
// { code: 'SVC_MOBILE_DATA_25GB', status: 'ACTIVE' }, // MANDATORY
// { code: 'SVC_VOICEMAIL', status: 'ACTIVE' }, // OPTIONAL (requested)
// { code: 'SVC_INTL_ROAMING', status: 'ACTIVE' } // OPTIONAL (requested)
// ]
// }
Core Concepts
Service Hierarchy Structure
interface ServiceHierarchy {
code: string; // Service code (e.g., 'SVC_MOBILE_BASE')
description: string; // Human-readable name
role: ServiceRole; // PRIMARY, INVENTORY, ADDON, REGISTRY
parentServiceCode?: string; // Parent service code (null if root)
inclusionType?: ServiceInclusionType; // MANDATORY, OPTIONAL, CONFIGURABLE
sortOrder?: number; // Display order (1-999)
children: ServiceHierarchy[]; // Nested children (recursive)
}
enum ServiceRole {
PRIMARY = 'PRIMARY', // Root subscription service
INVENTORY = 'INVENTORY', // Allocates resource (MSN, SIM, Modem)
ADDON = 'ADDON', // Adds functionality (Data, Voicemail)
REGISTRY = 'REGISTRY' // Compliance tracking (Number Registry)
}
// TypeScript type alias (maps to Commerce enum)
type ServiceInclusionType = 'MANDATORY' | 'OPTIONAL' | 'CONFIGURABLE'
// MANDATORY: Always included (auto-activated)
// OPTIONAL: User chooses (manual activation)
// CONFIGURABLE: Rule-based (system-determined)
Hierarchy Depth by Service Type
| Service Type | Depth | Structure | Example |
|---|---|---|---|
| Mobile | 2-3 levels | Flat | BASE → MSN/SIM/DATA |
| Internet | 3 levels | Nested | BASE → ACCESS → AVC |
| VoIP | 2-3 levels | Flat | BASE → VOIP_NUMBER/VOIP_DATA |
| Landline | 2-3 levels | Flat | BASE → LANDLINE_NUMBER |
Key Difference: Internet has NESTED hierarchy (NBN Access → NBN AVC), others are flat.
API Reference
Get Service Hierarchy
Retrieve complete service hierarchy including all children (recursive).
Endpoint: GET /api/catalog/services/{serviceCode}/hierarchy
Parameters:
serviceCode(path, required): Service code to retrieve hierarchy fordepth(query, optional): Maximum depth to retrieve (default: unlimited)includeInactive(query, optional): Include inactive services (default: false)
Response:
interface ServiceHierarchyResponse {
code: string;
description: string;
role: ServiceRole;
parentServiceCode: string | null;
inclusionType: ServiceInclusionType | null;
sortOrder: number | null;
active: boolean;
children: ServiceHierarchyResponse[];
}
Example Request:
curl -X GET \
'https://api.staqr.io/v1/catalog/services/SVC_MOBILE_BASE/hierarchy' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json'
Example Response:
{
"code": "SVC_MOBILE_BASE",
"description": "Mobile Base Service",
"role": "PRIMARY",
"parentServiceCode": null,
"inclusionType": null,
"sortOrder": null,
"active": true,
"children": [
{
"code": "SVC_MSN_STANDARD",
"description": "Standard MSISDN",
"role": "INVENTORY",
"parentServiceCode": "SVC_MOBILE_BASE",
"inclusionType": "MANDATORY",
"sortOrder": 1,
"active": true,
"children": []
},
{
"code": "SVC_SIM_PHYSICAL",
"description": "Physical SIM Card",
"role": "INVENTORY",
"parentServiceCode": "SVC_MOBILE_BASE",
"inclusionType": "MANDATORY",
"sortOrder": 2,
"active": true,
"children": []
},
{
"code": "SVC_VOICEMAIL",
"description": "Voicemail Service",
"role": "ADDON",
"parentServiceCode": "SVC_MOBILE_BASE",
"inclusionType": "OPTIONAL",
"sortOrder": 4,
"active": true,
"children": [
{
"code": "SVC_VOICEMAIL_BASIC",
"description": "Basic Voicemail",
"role": "ADDON",
"parentServiceCode": "SVC_VOICEMAIL",
"inclusionType": "OPTIONAL",
"sortOrder": 1,
"active": true,
"children": []
}
]
}
]
}
Validate Service Eligibility
Validate whether a service can be added to a parent service based on eligibility constraints.
Endpoint: POST /api/catalog/services/validate-eligibility
Request Body:
interface EligibilityRequest {
serviceCode: string; // Service to validate (e.g., 'SVC_STATIC_IP')
parentServiceId: string; // Parent service instance ID
customerId: string; // Customer ID
}
Response:
interface EligibilityResponse {
eligible: boolean; // Can service be added?
constraints: ConstraintResult[]; // Constraint evaluation results
message?: string; // Error message if not eligible
}
interface ConstraintResult {
type: 'REQUIRES' | 'EXCLUDES' | 'REQUIRES_PARENT_TYPE';
targetCode?: string;
satisfied: boolean;
message: string;
}
Example Request:
curl -X POST \
'https://api.staqr.io/v1/catalog/services/validate-eligibility' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"serviceCode": "SVC_STATIC_IP",
"parentServiceId": "svc_inst_123",
"customerId": "cust_456"
}'
Example Response (Eligible):
{
"eligible": true,
"constraints": [
{
"type": "REQUIRES",
"targetCode": "SVC_NBN_ACCESS",
"satisfied": true,
"message": "Static IP requires NBN Access (satisfied)"
}
]
}
Example Response (Not Eligible):
{
"eligible": false,
"constraints": [
{
"type": "REQUIRES",
"targetCode": "SVC_NBN_ACCESS",
"satisfied": false,
"message": "Static IP requires NBN Access (not active)"
}
],
"message": "Service not eligible. Static IP requires NBN Access service to be active."
}
Activate Service with Children
Activate parent service and automatically activate MANDATORY children.
Endpoint: POST /api/services/activate
Request Body:
interface ServiceActivationRequest {
customerId: string; // Customer ID
serviceCode: string; // Parent service code (e.g., 'SVC_MOBILE_BASE')
optionalChildren?: OptionalChild[]; // OPTIONAL children to activate
customFields?: Record<string, any>; // Custom field values
}
interface OptionalChild {
code: string; // Child service code
customFields?: Record<string, any>; // Custom field values for child
}
Response:
interface ServiceActivationResponse {
serviceId: string; // Parent service instance ID
status: ServiceStatus; // Service status (PENDING, ACTIVE)
children: ChildActivationResult[]; // Activated children
}
interface ChildActivationResult {
serviceId: string;
code: string;
status: ServiceStatus;
role: ServiceRole;
inclusionType: ServiceInclusionType;
}
enum ServiceStatus {
PENDING = 'PENDING',
ACTIVE = 'ACTIVE',
SUSPENDED = 'SUSPENDED',
TERMINATED = 'TERMINATED'
}
Example Request (Mobile with Optional Children):
curl -X POST \
'https://api.staqr.io/v1/services/activate' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"customerId": "cust_123",
"serviceCode": "SVC_MOBILE_BASE",
"optionalChildren": [
{ "code": "SVC_VOICEMAIL" },
{ "code": "SVC_INTL_ROAMING" }
]
}'
Example Response:
{
"serviceId": "svc_inst_789",
"status": "ACTIVE",
"children": [
{
"serviceId": "svc_inst_790",
"code": "SVC_MSN_STANDARD",
"status": "ACTIVE",
"role": "INVENTORY",
"inclusionType": "MANDATORY"
},
{
"serviceId": "svc_inst_791",
"code": "SVC_SIM_PHYSICAL",
"status": "ACTIVE",
"role": "INVENTORY",
"inclusionType": "MANDATORY"
},
{
"serviceId": "svc_inst_792",
"code": "SVC_MOBILE_DATA_25GB",
"status": "ACTIVE",
"role": "ADDON",
"inclusionType": "MANDATORY"
},
{
"serviceId": "svc_inst_793",
"code": "SVC_VOICEMAIL",
"status": "ACTIVE",
"role": "ADDON",
"inclusionType": "OPTIONAL"
},
{
"serviceId": "svc_inst_794",
"code": "SVC_INTL_ROAMING",
"status": "ACTIVE",
"role": "ADDON",
"inclusionType": "OPTIONAL"
}
]
}
Terminate Service (Cascade)
Terminate parent service and automatically terminate all children (lifecycle inheritance).
Endpoint: DELETE /api/services/{serviceId}
Parameters:
serviceId(path, required): Service instance ID to terminateconfirmCascade(query, optional): Confirm cascade termination (default: false)
Response:
interface ServiceTerminationResponse {
serviceId: string;
status: 'TERMINATED';
terminatedChildren: string[]; // Child service instance IDs terminated
terminatedAt: string; // ISO 8601 timestamp
}
Example Request:
curl -X DELETE \
'https://api.staqr.io/v1/services/svc_inst_789?confirmCascade=true' \
-H 'Authorization: Bearer YOUR_API_KEY'
Example Response:
{
"serviceId": "svc_inst_789",
"status": "TERMINATED",
"terminatedChildren": [
"svc_inst_790",
"svc_inst_791",
"svc_inst_792",
"svc_inst_793",
"svc_inst_794"
],
"terminatedAt": "2026-01-29T10:30:00Z"
}
Error Response (Missing Confirmation):
{
"error": "CONFIRMATION_REQUIRED",
"message": "This service has 5 child services. Terminating this service will also terminate all children. Set confirmCascade=true to proceed.",
"childCount": 5
}
Integration Patterns
Pattern 1: Mobile Service Activation
Use Case: Activate mobile service with SIM, MSISDN, and optional voicemail.
async function activateMobileService(
customerId: string,
includeVoicemail: boolean = false
): Promise<ServiceActivationResponse> {
const client = new StaqrClient({ apiKey: process.env.STAQR_API_KEY });
const optionalChildren = [];
if (includeVoicemail) {
optionalChildren.push({ code: 'SVC_VOICEMAIL' });
}
return await client.services.activate({
customerId,
serviceCode: 'SVC_MOBILE_BASE',
optionalChildren
});
}
// Usage
const result = await activateMobileService('cust_123', true);
console.log(`Activated service ${result.serviceId} with ${result.children.length} children`);
Pattern 2: Internet Service Activation (Nested Hierarchy)
Use Case: Activate NBN service with nested NBN Access → NBN AVC hierarchy.
async function activateInternetService(
customerId: string,
includeStaticIP: boolean = false
): Promise<ServiceActivationResponse> {
const client = new StaqrClient({ apiKey: process.env.STAQR_API_KEY });
const optionalChildren = [];
if (includeStaticIP) {
// Static IP requires NBN Access (validated automatically)
optionalChildren.push({ code: 'SVC_STATIC_IP' });
}
// NBN Access and NBN AVC are MANDATORY, activated automatically
return await client.services.activate({
customerId,
serviceCode: 'SVC_NBN_BASE',
optionalChildren
});
}
// Usage
const result = await activateInternetService('cust_456', true);
// Verify 3-level hierarchy created (BASE → ACCESS → AVC)
const hierarchy = await client.catalog.getServiceHierarchy('SVC_NBN_BASE');
const access = hierarchy.children.find(c => c.code === 'SVC_NBN_ACCESS');
const avc = access.children.find(c => c.code === 'SVC_NBN_AVC');
console.log(`Nested hierarchy: ${hierarchy.code} → ${access.code} → ${avc.code}`);
CRITICAL: Internet activation does NOT require NR_* fields (Number Registry removed in Phase 41-C-01).
Pattern 3: Eligibility Validation Before Activation
Use Case: Check if service can be added before attempting activation.
async function addServiceIfEligible(
serviceCode: string,
parentServiceId: string,
customerId: string
): Promise<ServiceActivationResponse | null> {
const client = new StaqrClient({ apiKey: process.env.STAQR_API_KEY });
// 1. Validate eligibility
const eligibility = await client.catalog.validateEligibility({
serviceCode,
parentServiceId,
customerId
});
if (!eligibility.eligible) {
console.error(`Service not eligible: ${eligibility.message}`);
// Log constraint failures
for (const constraint of eligibility.constraints.filter(c => !c.satisfied)) {
console.error(` - ${constraint.type}: ${constraint.message}`);
}
return null;
}
// 2. Activate if eligible
return await client.services.activateChild({
serviceCode,
parentServiceId,
customerId
});
}
// Usage
const result = await addServiceIfEligible(
'SVC_STATIC_IP',
'svc_inst_123', // NBN Base service
'cust_789'
);
Pattern 4: Lifecycle Management (Suspend/Reactivate)
Use Case: Suspend service and all children, then reactivate later.
async function suspendService(serviceId: string): Promise<void> {
const client = new StaqrClient({ apiKey: process.env.STAQR_API_KEY });
// Suspend parent (children suspend automatically via lifecycle inheritance)
await client.services.suspend(serviceId);
console.log(`Suspended service ${serviceId} and all children`);
}
async function reactivateService(serviceId: string): Promise<void> {
const client = new StaqrClient({ apiKey: process.env.STAQR_API_KEY });
// Reactivate parent (children reactivate automatically via lifecycle inheritance)
await client.services.reactivate(serviceId);
console.log(`Reactivated service ${serviceId} and all children`);
}
// Usage
await suspendService('svc_inst_123'); // Service and children SUSPENDED
// ... time passes ...
await reactivateService('svc_inst_123'); // Service and children ACTIVE again
Pattern 5: Query Service Hierarchy for Display
Use Case: Retrieve and display complete service hierarchy in UI.
async function displayServiceHierarchy(serviceCode: string): Promise<void> {
const client = new StaqrClient({ apiKey: process.env.STAQR_API_KEY });
const hierarchy = await client.catalog.getServiceHierarchy(serviceCode);
function printHierarchy(service: ServiceHierarchy, depth: number = 0): void {
const indent = ' '.repeat(depth);
const inclusionBadge = service.inclusionType
? `[${service.inclusionType}]`
: '';
console.log(
`${indent}${service.code} - ${service.description} ` +
`${inclusionBadge} (${service.role})`
);
// Sort children by sortOrder
const sortedChildren = service.children.sort((a, b) =>
(a.sortOrder || 999) - (b.sortOrder || 999)
);
for (const child of sortedChildren) {
printHierarchy(child, depth + 1);
}
}
printHierarchy(hierarchy);
}
// Usage
await displayServiceHierarchy('SVC_MOBILE_BASE');
// Output:
// SVC_MOBILE_BASE - Mobile Base Service (PRIMARY)
// SVC_MSN_STANDARD - Standard MSISDN [MANDATORY] (INVENTORY)
// SVC_SIM_PHYSICAL - Physical SIM Card [MANDATORY] (INVENTORY)
// SVC_MOBILE_DATA_25GB - 25GB Data Allowance [MANDATORY] (ADDON)
// SVC_VOICEMAIL - Voicemail Service [OPTIONAL] (ADDON)
// SVC_VOICEMAIL_BASIC - Basic Voicemail [OPTIONAL] (ADDON)
// SVC_VOICEMAIL_VISUAL - Visual Voicemail [OPTIONAL] (ADDON)
Error Handling
Common Errors
1. Service Not Eligible
{
"error": "SERVICE_NOT_ELIGIBLE",
"message": "Service SVC_STATIC_IP cannot be added. Static IP requires NBN Access service to be active.",
"code": "SVC_STATIC_IP",
"constraints": [
{
"type": "REQUIRES",
"targetCode": "SVC_NBN_ACCESS",
"satisfied": false
}
]
}
Solution: Activate required service first, then retry.
2. Orphaned Service (No Parent)
{
"error": "INVALID_HIERARCHY",
"message": "Service SVC_MSN_STANDARD requires parent service. Role INVENTORY cannot be root service.",
"code": "SVC_MSN_STANDARD",
"role": "INVENTORY"
}
Solution: Provide parentServiceId when activating INVENTORY/ADDON/REGISTRY services.
3. Conflicting Services (Mutual Exclusivity)
{
"error": "SERVICE_NOT_ELIGIBLE",
"message": "Service SVC_MSN_GOLD cannot be added. Cannot have multiple MSISDN tiers. Choose Gold OR Silver, not both.",
"code": "SVC_MSN_GOLD",
"constraints": [
{
"type": "EXCLUDES",
"targetCode": "SVC_MSN_SILVER",
"satisfied": false
}
]
}
Solution: Terminate conflicting service before adding new service.
TypeScript SDK
Installation
npm install @staqr/sdk
Initialisation
import { StaqrClient } from '@staqr/sdk';
const client = new StaqrClient({
apiKey: process.env.STAQR_API_KEY,
environment: 'production' // or 'sandbox'
});
Service Activation
const result = await client.services.activate({
customerId: 'cust_123',
serviceCode: 'SVC_MOBILE_BASE',
optionalChildren: [
{ code: 'SVC_VOICEMAIL' }
]
});
Hierarchy Queries
const hierarchy = await client.catalog.getServiceHierarchy('SVC_MOBILE_BASE');
Eligibility Validation
const eligibility = await client.catalog.validateEligibility({
serviceCode: 'SVC_STATIC_IP',
parentServiceId: 'svc_inst_123',
customerId: 'cust_456'
});
Webhook Events
Subscribe to service lifecycle events via webhooks.
service.activated
{
"event": "service.activated",
"timestamp": "2026-01-29T10:30:00Z",
"data": {
"serviceId": "svc_inst_789",
"code": "SVC_MOBILE_BASE",
"customerId": "cust_123",
"status": "ACTIVE",
"children": [
{ "serviceId": "svc_inst_790", "code": "SVC_MSN_STANDARD" },
{ "serviceId": "svc_inst_791", "code": "SVC_SIM_PHYSICAL" }
]
}
}
service.terminated
{
"event": "service.terminated",
"timestamp": "2026-01-29T11:00:00Z",
"data": {
"serviceId": "svc_inst_789",
"code": "SVC_MOBILE_BASE",
"customerId": "cust_123",
"status": "TERMINATED",
"terminatedChildren": ["svc_inst_790", "svc_inst_791", "svc_inst_792"]
}
}
Related Documentation
- Authentication Guide - API authentication
- Admin Guide: Service Hierarchies - Configure hierarchies
Need Help?
- Developer Support: dev-support@privdr.com
- API Documentation: https://api.staqr.io/docs
- GitHub Examples: https://github.com/privdr/staqr-examples