Skip to main content

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 TypeDepthStructureExample
Mobile2-3 levelsFlatBASE → MSN/SIM/DATA
Internet3 levelsNestedBASE → ACCESS → AVC
VoIP2-3 levelsFlatBASE → VOIP_NUMBER/VOIP_DATA
Landline2-3 levelsFlatBASE → 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 for
  • depth (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 terminate
  • confirmCascade (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"]
}
}


Need Help?