TMForum Product Catalog API Implementation
RESTful microservices implementation of TMForum Open APIs (TMF620, TMF637, TMF622) for managing telecom product catalogs, orders, and inventory with complete lifecycle management.
Project Overview
Implemented TMForum Open API standards (TMF620 Product Catalog, TMF637 Product Inventory, TMF622 Product Ordering) to enable B2B integration and expose cellular plan management capabilities.
Built microservices-based architecture supporting complete product lifecycle from catalog management through ordering, activation, and inventory tracking for mobile plans, add-ons, and devices.
Handles 50,000+ API calls per day supporting partner integrations, internal BSS systems, and self-service portals.
The Challenge
Problem Statement
This project addressed multiple critical technical challenges:
1. TMForum SID Model Complexity
The TMForum Shared Information/Data (SID) model is highly abstract with complex relationships between Product Offerings, Product Specifications, Products, and Product Inventory. Implementing a simplified yet compliant model required deep understanding of telecom business processes.
2. Product Lifecycle State Management
Products transition through multiple states (Feasibility Check → Design → Active → Suspended → Terminated). Managing state transitions, validating allowed transitions, and ensuring data consistency across Product Catalog, Ordering, and Inventory APIs was complex.
Solution Architecture
(Partner/BSS/Portal)"] APIGW["AWS API Gateway
(OAuth 2.0 + Rate Limiting)"] subgraph TMForum["TMForum APIs"] TMF620["TMF620
Product Catalog"] TMF637["TMF637
Product Inventory"] TMF622["TMF622
Product Ordering"] end subgraph Backend["Backend Services"] CatalogSvc["Catalog Service
(Lambda)"] InventorySvc["Inventory Service
(Lambda)"] OrderSvc["Order Service
(Lambda)"] end subgraph DataLayer["Data Layer"] CatalogDB[("Product Catalog
DynamoDB")] InventoryDB[("Product Inventory
DynamoDB")] OrderDB[("Orders
DynamoDB")] end EventBus["EventBridge
(Lifecycle Events)"] Client --> APIGW APIGW --> TMF620 APIGW --> TMF637 APIGW --> TMF622 TMF620 --> CatalogSvc TMF637 --> InventorySvc TMF622 --> OrderSvc CatalogSvc --> CatalogDB InventorySvc --> InventoryDB OrderSvc --> OrderDB OrderSvc --> EventBus EventBus --> InventorySvc style Client fill:#667eea,stroke:#333,stroke-width:2px,color:#fff style APIGW fill:#48bb78,stroke:#333,stroke-width:2px,color:#fff style TMF620 fill:#ed8936,stroke:#333,stroke-width:2px,color:#fff style TMF637 fill:#ed8936,stroke:#333,stroke-width:2px,color:#fff style TMF622 fill:#ed8936,stroke:#333,stroke-width:2px,color:#fff style EventBus fill:#9f7aea,stroke:#333,stroke-width:2px,color:#fff
Key Features & Implementation
- TMF620 Product Catalog Management: Create, update, and manage product offerings with pricing, terms, and bundling rules
- TMF637 Product Inventory: Track activated products per subscriber with status, characteristics, and relationships
- TMF622 Product Ordering: Handle order lifecycle (Acknowledged → InProgress → Completed → Failed) with validation
- OAuth 2.0 Security: Partner authentication using client credentials flow with scoped access
- Event-Driven Architecture: EventBridge publishes lifecycle events (ProductOrderStateChangeEvent)
- API Versioning: Support multiple API versions simultaneously for backward compatibility
- Rate Limiting: AWS API Gateway throttling (1000 req/sec burst, 5000 req/sec steady)
- Offer Consistency Validation: Ensure product specifications match catalog rules before activation
Implementation Highlights
1. TMF620 Product Catalog API Handler
API handler for managing product offerings in the catalog:
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, PutCommand, GetCommand } from '@aws-sdk/lib-dynamodb';
interface ProductOffering {
id: string;
href: string;
name: string;
description: string;
version: string;
lifecycleStatus: 'In design' | 'In test' | 'Active' | 'Launched' | 'Retired' | 'Obsolete';
validFor: {
startDateTime: string;
endDateTime?: string;
};
productSpecification: {
id: string;
name: string;
};
productOfferingPrice: Array<{
name: string;
price: {
taxIncludedAmount: number;
dutyFreeAmount: number;
unit: string;
};
priceType: 'recurring' | 'one time' | 'usage';
recurringChargePeriod?: 'month' | 'year';
}>;
category: Array<{
id: string;
name: string;
}>;
}
export class ProductCatalogService {
private docClient: DynamoDBDocumentClient;
private tableName: string;
constructor() {
const client = new DynamoDBClient({});
this.docClient = DynamoDBDocumentClient.from(client);
this.tableName = process.env.CATALOG_TABLE!;
}
async createProductOffering(offering: ProductOffering): Promise<ProductOffering> {
// Validate lifecycle status transition
this.validateLifecycleStatus(offering.lifecycleStatus);
// Generate ID and href
offering.id = this.generateId();
offering.href = `/productCatalogManagement/v4/productOffering/${offering.id}`;
await this.docClient.send(new PutCommand({
TableName: this.tableName,
Item: offering,
ConditionExpression: 'attribute_not_exists(id)'
}));
return offering;
}
async getProductOffering(id: string): Promise<ProductOffering | null> {
const result = await this.docClient.send(new GetCommand({
TableName: this.tableName,
Key: { id }
}));
return result.Item as ProductOffering || null;
}
private validateLifecycleStatus(status: string): void {
const validStatuses = ['In design', 'In test', 'Active', 'Launched', 'Retired', 'Obsolete'];
if (!validStatuses.includes(status)) {
throw new Error(`Invalid lifecycle status: ${status}`);
}
}
private generateId(): string {
return `PO-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
}
export const handler = async (
event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {
const service = new ProductCatalogService();
try {
if (event.httpMethod === 'POST') {
const offering = JSON.parse(event.body!);
const created = await service.createProductOffering(offering);
return {
statusCode: 201,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(created)
};
} else if (event.httpMethod === 'GET') {
const id = event.pathParameters?.id!;
const offering = await service.getProductOffering(id);
if (!offering) {
return {
statusCode: 404,
body: JSON.stringify({ message: 'Product offering not found' })
};
}
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(offering)
};
}
return {
statusCode: 405,
body: JSON.stringify({ message: 'Method not allowed' })
};
} catch (error: any) {
console.error('Error:', error);
return {
statusCode: error.statusCode || 500,
body: JSON.stringify({ message: error.message })
};
}
};
2. Product Order State Machine
Managing product order lifecycle with state validation:
import { EventBridgeClient, PutEventsCommand } from '@aws-sdk/client-eventbridge';
type OrderState =
| 'Acknowledged'
| 'Rejected'
| 'Pending'
| 'Held'
| 'InProgress'
| 'Cancelled'
| 'Completed'
| 'Failed'
| 'Partial';
interface ProductOrder {
id: string;
state: OrderState;
orderDate: string;
completionDate?: string;
productOrderItem: Array<{
id: string;
quantity: number;
action: 'add' | 'modify' | 'delete';
state: OrderState;
product: {
id: string;
name: string;
productOffering: { id: string };
};
}>;
}
export class OrderStateMachine {
private eventBridge: EventBridgeClient;
private allowedTransitions: Record<OrderState, OrderState[]> = {
'Acknowledged': ['Rejected', 'Pending', 'InProgress'],
'Rejected': [],
'Pending': ['Held', 'InProgress', 'Cancelled'],
'Held': ['InProgress', 'Cancelled'],
'InProgress': ['Cancelled', 'Completed', 'Failed', 'Partial'],
'Cancelled': [],
'Completed': [],
'Failed': [],
'Partial': ['Completed', 'Failed']
};
constructor() {
this.eventBridge = new EventBridgeClient({});
}
async transitionOrderState(
order: ProductOrder,
newState: OrderState
): Promise<ProductOrder> {
// Validate transition is allowed
if (!this.isTransitionAllowed(order.state, newState)) {
throw new Error(
`Invalid state transition from ${order.state} to ${newState}`
);
}
const previousState = order.state;
order.state = newState;
if (newState === 'Completed') {
order.completionDate = new Date().toISOString();
}
// Publish state change event
await this.publishStateChangeEvent(order, previousState, newState);
return order;
}
private isTransitionAllowed(currentState: OrderState, newState: OrderState): boolean {
return this.allowedTransitions[currentState]?.includes(newState) || false;
}
private async publishStateChangeEvent(
order: ProductOrder,
previousState: OrderState,
newState: OrderState
): Promise<void> {
await this.eventBridge.send(new PutEventsCommand({
Entries: [{
Source: 'tmf.productordering',
DetailType: 'ProductOrderStateChangeEvent',
Detail: JSON.stringify({
eventId: `evt-${Date.now()}`,
eventTime: new Date().toISOString(),
eventType: 'ProductOrderStateChangeEvent',
event: {
productOrder: {
id: order.id,
state: newState,
previousState: previousState,
href: `/productOrderingManagement/v4/productOrder/${order.id}`
}
}
})
}]
}));
}
}
Results & Impact
Outcomes Achieved
- API Adoption: 15+ B2B partners integrated within 6 months
- Order Processing: 10,000+ product orders per day with 99.5% success rate
- Response Time: Average API latency of 120ms for catalog queries, 300ms for orders
- TMForum Conformance: Passed TMForum Open API CTK (Conformance Testing Kit) validation
- Revenue Enablement: $2M+ in partner-driven sales through API integrations
- Reduced Integration Time: From 3 months to 2 weeks for new partner onboarding
Technical Insights & Best Practices
TMForum SID Model Implementation
- Product Offering is in catalog (available to buy); Product is in inventory (activated/subscribed)
- Product Specification defines technical characteristics; Product Offering adds pricing and terms
- Product relationships (bundling, dependencies) must be validated during ordering
- Lifecycle states differ between catalog (In design → Active → Retired) and inventory (Feasibility → Active → Suspended)
API Design Best Practices
- Use HATEOAS links in responses for resource navigation (href fields)
- Implement filtering with query parameters (?fields=id,name&lifecycleStatus=Active)
- Version APIs in URL path (/v4/) to support breaking changes
- Return HTTP 201 with Location header for resource creation
Standards & References
- TMF620 Product Catalog Management API
- TMF637 Product Inventory Management API
- TMF622 Product Ordering Management API
- TMForum SID (Shared Information/Data Model)
- OpenAPI Specification 3.0
- OAuth 2.0 RFC 6749
- REST API Design Guidelines