#5. CP Registry API
Connected Services Framework (CSF) — Part 2: Implementation — Version 2.0
#5.1 Overview
Each MAP publishes a CP Registry — a JSON document containing everything another MAP needs to know about the publishing MAP and its connected CPs. Other MAPs collect these CP Registries via a REST API endpoint and converge them into a private Master Registry. From the Master Registry, each MAP derives a filtered Directory for its own CPs.
Design intent — superset of TOTSCo's directory. The CSF CP Registry is deliberately a strict superset of the TOTSCo OTS/GPLB directory structure so that TOTSCo (and any party that already consumes the TOTSCo directory) can ingest the CSF Registry with its existing parser. CSF-specific additions —
signing[], expandedresource[],contact[],outage[], and the routing-group metadata in §5.4.1 — are additive. Parsers that don't recognise these fields ignore them; the underlying TOTSCo-compatible shape is unchanged. This was an explicit design decision intended to make industry-wide consumption trivial through the JSON contract.
#5.1.1 Side-by-side — TOTSCo vs CSF registry shape
TOTSCo format (OTS / GPLB today):
"list": [
{
"listType": "RCPID",
"identity": [
{
"id": "RTLL",
"name": "NowYoYo",
"processSupport": [
{
"process": "OTS",
"status": "ACTIVE"
}
],
"resource": [
{
"name": "salesAssistURL",
"type": "URL",
"value": "https://nowyoyo.com/ots/sales-assistance/"
},
{
"name": "customerAssistURL",
"type": "URL",
"value": "https://nowyoyo.com/ots/customer-assistance/"
}
]
}
]
}
]
CSF format (superset — extra fields are additive, never breaking):
"list": [
{
"identity": [
{
"id": "c42569a5-d0ed-46f7-b9df-4ea0ba6a29c8",
"name": "Red Company ISP",
"processSupport": [
{
"process": "GPLB",
"status": "ACTIVE"
}
],
"resource": [
{
"name": "customerAssistURL",
"type": "URL",
"value": "https://gplb.red-company.com/customerAssistURL.html"
}
],
"signing": [
{
"domain": "gplb-test.nowyoyo.net",
"routingID": [
"businessSwitch.*"
]
}
]
}
]
}
]
The
idis wider in the CSF (UUIDv4 instead of 4-character) but both are validstringvalues under the JAM Specification (up to 256 characters). Thesigning[]block is CSF-only; TOTSCo can ignore it. CSF MAPs publish the sameprocessSupport[].processvalues (OTS,GPLB) that TOTSCo already uses, so process-based filtering is identical on both sides.
#5.1.2 Dual-process filtering — same URL pattern across both networks
TOTSCo's pre-production Directory v2 already combines OTS and GPLB entries in one list, distinguishing them by processSupport[].process. MAPs that want a single-process view filter on an identity query parameter:
https://preprod.otshub.totsco.co.uk/directory/v2/entry?listType=RCPID&identity=all
https://preprod.otshub.totsco.co.uk/directory/v2/entry?listType=RCPID&identity=ots
https://preprod.otshub.totsco.co.uk/directory/v2/entry?listType=RCPID&identity=gplb
We assume the same filter pattern in production. The CSF's Master Registry follows the same convention — processSupport[].process carries OTS, GPLB, and future process tags — so any TOTSCo-side filtering logic carries over unchanged when consuming a CSF CP Registry.
#5.2 API Specification
| Field | Value |
|---|---|
| HTTP Method | GET |
| Authentication | OAuth 2.0 Bearer Token (same credentials as messaging) |
| Response Format | application/json |
| Transport Security | HTTPS, TLS 1.3 |
#5.3 Registry JSON Structure
The registry has two main sections: map (MAP metadata) and list (CP directory entries).
#Complete Example
{
"map": {
"name": "myMap",
"version": "2024-01-01T13:45:24Z00000",
"registrationURI": "https://apply.mymapdomain.com/register",
"serviceStatusURI": "https://status.mymapdomain.com/myStatus",
"serviceStatusServiceURI": "https://status.mymapdomain.com/myStatusAPI",
"contact": [
{
"type": "phone",
"value": "01111 111111",
"purpose": ["support", "sales"],
"availabilityFrom": "09:00",
"availabilityTo": "17:00",
"availabilityDays": ["monday", "tuesday", "wednesday", "thursday", "friday"],
"comment": "Use for all support calls. Support hours 9am to 5pm."
},
{
"type": "email",
"value": "support@myMapDomain.com"
},
{
"type": "url",
"value": "https://support.mymapdomain.com/support"
}
],
"connectivity": {
"directory": {
"url": "https://api.mymapdomain.com/directory",
"oauthTokenURL": "https://api.mymapdomain.com/token"
},
"services": [
{
"description": "Endpoint for OTS messages",
"service": "messaging",
"url": "https://api.mymapdomain.com/letterboxOTS",
"oauthTokenURL": "https://api.mymapdomain.com/token",
"routingGroups": ["OTSMessages"]
},
{
"description": "Endpoint for SforB (GPLB) messages",
"service": "messaging",
"url": "https://api.mymapdomain.com/letterboxGPLB",
"oauthTokenURL": "https://api.mymapdomain.com/token",
"routingGroups": ["GPLBMessages"]
}
]
},
"routingGroups": [
{
"name": "OTSMessages",
"routingID": ["residentialSwitch.*"]
},
{
"name": "GPLBMessages",
"routingID": ["businessSwitch.*"]
}
],
"connections": [
{
"name": "MAP1",
"registrationURI": "https://apply.map1.com/register"
},
{
"name": "MAP2",
"registrationURI": "https://apply.map2.com/register"
}
],
"sponsorship": {
"order": 3,
"nextSponsor": 4,
"nextSponsorUpdated": "2026-06-09T10:00:00Z",
"capacity": 2,
"registrationURI": "https://apply.mymapdomain.com/become-a-map",
"active": [
{
"id": "a3f91c2d-7e4b-4f8a-b1c2-9d3e5f6a7b8c",
"type": "traditional",
"opened": "2026-06-09T10:00:00Z",
"candidateRef": "ONB-2026-0042"
}
]
}
},
"list": [
{
"listType": "RCPID",
"identity": [
{
"id": "d8322d80-92c8-4906-a408-f1d2daf7e03d",
"name": "Xenon Telecom",
"processSupport": [
{ "process": "OTS_Porting", "status": "Active" },
{ "process": "GPLB", "status": "Active" }
],
"resource": [
{ "name": "customerAssistURL", "type": "URL", "value": "https://xenon.com/SforB" },
{ "name": "salesAssistURL", "type": "URL", "value": "https://xenon.com/sales" }
],
"signing": [
{
"domain": "ots.xenon.com",
"routingID": ["residentialSwitch.*"]
},
{
"domain": "gplb.xenon.com",
"routingID": ["businessSwitch.*", "notification"]
}
],
"contact": [
{
"type": "phone",
"value": "020 7946 0123",
"purpose": ["sales"],
"availabilityFrom": "09:00",
"availabilityTo": "17:00",
"availabilityDays": ["monday", "tuesday", "wednesday", "thursday", "friday"],
"comment": "Sales team available weekdays 9am-5pm"
},
{
"type": "email",
"value": "switching-support@xenon.com",
"purpose": ["support", "technical"],
"comment": "24/7 email support — response within core business hours"
},
{
"type": "url",
"value": "https://xenon.com/switching-support",
"purpose": ["support"],
"comment": "Online ticketing system for switch query resolution"
}
]
}
]
}
]
}
TOTSCo maps the CSF Source UUIDv4 values to their own pseudo-created 4-digit character equivalents, which can be done transparently with its own managed clients. The CSF network does not need to know what these 4-digit characters are. When TOTSCo sends a message to the CSF, they will translate their own Source 4-digit RCPID to the required CSF UUIDv4 RCPID before routing the message using the Master CSF directory that they have compiled.
#MAP in TEST Mode — Onboarding Example
When a new MAP is undergoing onboarding, its registry SHOULD include map.status set to TEST. Receiving MAPs MUST treat every CP under that MAP as test, regardless of the per-CP processSupport[].status, and exclude those CPs from the CP-facing Directory until the MAP transitions to ACTIVE. See Onboarding & Testing Process §4.2.
{
"map": {
"name": "newMap",
"status": "TEST",
"version": "2026-04-29T10:30:00Z00000",
"registrationURI": "https://apply.newmapdomain.com/register",
"...": "remainder of map object as per the main example"
},
"list": [ "..." ]
}
Note:
map.statusis optional. If absent, the MAP is treated asACTIVE. The valid values areACTIVE,TEST, andSUSPEND.
#5.4 MAP Section — Field Reference
| JSON Path | Description | Type | Required |
|---|---|---|---|
map | Container for MAP metadata | Object | Yes |
map/name | MAP name | String | Yes |
map/status | MAP-level operational status: ACTIVE, TEST, SUSPEND. If absent, treated as ACTIVE. When set to TEST, all CPs under this MAP are treated as test regardless of their own per-CP processSupport[].status. See Onboarding & Testing Process §4.2. | String (enum) | Optional |
map/version | Last updated timestamp. Used to determine if local cached values need changing | DateTime (yyyy-MM-ddTHH:mm:ssZSSSSS) | Yes |
map/registrationURI | URI for CP/MAP registration | String | Optional |
map/serviceStatusURI | URI for operational status page | String | Optional |
map/serviceStatusServiceURI | API URI for programmatic status checks | String | Optional |
map/contact[] | Contact methods for the MAP | Object Array | Yes |
map/contact[]/type | Contact type: phone, email, url | String (enum) | Yes |
map/contact[]/value | Contact value (number, address, or URL) | String | Yes |
map/contact[]/purpose[] | What the contact is for: support, sales, technical | String Array | Optional |
map/contact[]/availabilityFrom | Availability start time | Time (HH:mm) | Optional |
map/contact[]/availabilityTo | Availability end time | Time (HH:mm) | Optional |
map/contact[]/availabilityDays[] | Days available | String Array (day names) | Optional |
map/contact[]/comment | Usage notes | String | Optional |
map/outage[] | Array of current outage or planned maintenance entries. An empty array or absent field signals GREEN — no reported issues. MAPs implementing outage notification SHOULD always include this field, even as an empty array. See §9.4 Outage Notification. | Object Array | Yes (empty if no issues) |
map/outage[]/priority | ITIL incident priority: P1 (MAP unavailable — fix within 4 working hours), P2 (CP unavailable — fix within 1 working day), P3 (service degradation — fix within 2 working days). Primary severity indicator; replaces traffic light model. See §9.4.2. | String (enum) | Yes |
map/outage[]/type | Outage type: PLANNED or INCIDENT | String (enum) | Yes |
map/outage[]/start | Start time of the outage or maintenance window (ISO 8601 UTC) | DateTime | Yes |
map/outage[]/end | Expected or actual end time (ISO 8601 UTC). Omit for active incidents where resolution time is unknown. | DateTime | Optional |
map/outage[]/affectedServices | Routing group names (matching map/routingGroups[]/name) affected by this entry. If absent or empty, all services are considered affected. | String Array | Optional |
map/outage[]/description | Human-readable status narrative: what is affected, current status, ETA (if known), and any workaround. Updated as the incident evolves. | String | Yes |
map/outage[]/contact | Escalation contact specific to this outage. Uses the same structure as a map/contact[] entry. | Object | Optional |
map/outage[]/contact/type | Contact type: phone, email, or url | String (enum) | Yes (if contact present) |
map/outage[]/contact/value | Contact value (number, address, or URL) | String | Yes (if contact present) |
map/outage[]/contact/comment | Usage instructions — e.g. reference numbers, bridge PINs, or when to use this contact | String | Optional |
map/connectivity | Connection details | Object | Yes |
map/connectivity/directory/url | Directory API endpoint | String | Yes |
map/connectivity/directory/oauthTokenURL | OAuth token endpoint for directory access | String | Yes |
map/connectivity/services[] | Service endpoints | Object Array | Yes |
map/connectivity/services[]/description | Human-readable description | String | Yes |
map/connectivity/services[]/service | Service type (e.g., messaging) | String | Yes |
map/connectivity/services[]/url | Service endpoint URL | String | Yes |
map/connectivity/services[]/oauthTokenURL | OAuth token endpoint for this service | String | Yes |
map/connectivity/services[]/routingGroups[] | Routing groups served by this endpoint | String Array | Yes |
map/connectivity/services[]/relay | Whether this endpoint supports message relay | Boolean | Yes |
map/routingGroups[] | Routing group definitions | Object Array | Yes |
map/routingGroups[]/name | Routing group name (matches service routing groups) | String | Yes |
map/routingGroups[]/routingID[] | Accepted routing ID patterns (regex) | String Array | Yes |
map/connections[] | Other MAPs this MAP has connectivity with | Object Array | Yes |
map/connections[]/name | Name of the connected MAP | String | Yes |
map/connections[]/registrationURI | Registration URI of the connected MAP | String | Yes |
map/sponsorship | Sponsor rotation metadata for the decentralised onboarding of new MAPs. See §5.4.2 for the rotation rules and the Onboarding & Testing Process §3.4 for the full mechanism. | Object | Yes |
map/sponsorship/order | This MAP's permanent FIFO position in the rotation. Assigned at onboarding; never changes — preserved for historical audit consistency even when the MAP withdraws or its map/status becomes non-ACTIVE. The rotation skips entries whose map/status is not ACTIVE. | Integer | Yes |
map/sponsorship/nextSponsor | The order number of the MAP designated as the next Sponsor after this MAP. Updated when this MAP accepts a new sponsorship engagement. | Integer | Yes |
map/sponsorship/nextSponsorUpdated | Timestamp when nextSponsor was last updated (ISO 8601 UTC). The MAP with the most recent nextSponsorUpdated is the current active Sponsor. | DateTime | Yes |
map/sponsorship/capacity | Maximum concurrent sponsorships this MAP will accept. Minimum: 1; no upper bound — set to reflect the MAP's own operational throughput. A MAP that primarily takes single-cp (MAP-of-1) engagements MAY support a higher capacity because those complete faster than traditional multi-CP onboardings. | Integer | Yes |
map/sponsorship/registrationURI | URI advertising the Sponsor MAP's onboarding application page for prospective MAPs (self-service portal, contracted form, or structured registration endpoint). Omitted if the MAP uses non-electronic registration (email, telephone, ticketing); prospective MAPs then use the MAP's map/contact[] entries as the contact channel. | URI | Optional |
map/sponsorship/active[] | Array of currently open sponsorship engagements. Empty array when idle. | Object Array | Yes |
map/sponsorship/active[]/id | UUIDv4 identifier for this engagement, generated by the Sponsor MAP on acceptance. | String (UUID) | Yes |
map/sponsorship/active[]/type | Type of candidate: traditional (multi-CP MAP) or single-cp (MAP onboarding with a single CP, e.g. a MAP-of-1 migrating from TOTSCo SforB per Part 1 §7.4). Both types count as 1 against the MAP's capacity. | String (enum) | Yes |
map/sponsorship/active[]/opened | Timestamp when the Sponsor MAP accepted the engagement (ISO 8601 UTC). | DateTime | Yes |
map/sponsorship/active[]/candidateRef | Non-identifying reference assigned by the Sponsor MAP for its own coordination — for example a ticket number from the Sponsor's internal onboarding system when handling several concurrent engagements. Free-form string; not standardised across MAPs; consumers outside the issuing MAP MUST NOT rely on its semantics. | String | Optional |
#5.4.1 Routing Groups
In the MAP section the routingGroups in conjunction with connectivity/services[] are used to determine where to send a message.
In this snippet of the MAP object, there are 3 routing groups:
[
{
"name": "OTSMessages",
"routingID": [
"resi.*"
]
},
{
"name": "GPLBMessages",
"routingID": [
"businessSwitch.*"
]
},
{
"name": "FutureMessages",
"routingID": [
"futureThing.*"
]
}
]
The routingID is a standard regex pattern that is matched against the routingID in the envelope of the message to be sent. The sender uses this to determine the name of
the correct routing group. So a message with a routing ID of futureThingActivateRequest will match with the FutureMessages routing group.
This routing group name is then matched against the array connectivity/services[], specifically the routingGroups[] array.
Below is a snippet of the connectivity/services[] MAP object:
[
{
"description": "Endpoint for OTS messages",
"service": "messaging",
"url": "https://api.mymapdomain.com/letterboxOTS",
"oauthTokenURL": "https://api.mymapdomain.com/token",
"routingGroups": [
"OTSMessages"
]
},
{
"description": "Endpoint for Other messages",
"service": "messaging",
"url": "https://api.mymapdomain.com/letterbox",
"oauthTokenURL": "https://api.mymapdomain.com/token",
"routingGroups": [
"GPLBMessages", "FutureMessages"
]
}
]
By using the name calculated in the previous step, FutureMessages is matched against and resolves to the Endpoint for Other messages.
This contains a Auth and Message URL allowing the message to sent to the MAP in question.
This is a simple example of how routing groups can be used to determine where to send a message. Some MAPs may use the same endpoints for all messages, while others may a different endpoint for each message type. It provides maximum flexibility.
#5.4.2 Sponsorship Object — Rotation Rules
The sponsorship object enables MAPs to coordinate a fair, decentralised rotation for onboarding new MAPs onto the CSF. The detailed mechanism (acceptance-advance, chain-walk overflow, edge-case handling, worked examples) is set out in the Onboarding & Testing Process §3.4. This section sets out the rules that bind every MAP's implementation of the sponsorship object.
| Rule | Statement |
|---|---|
| Capacity bounds | The minimum permitted capacity is 1 — every onboarded MAP MUST be willing to take at least one engagement at a time. There is no upper bound on capacity; a MAP sets the value to reflect its own operational throughput. A MAP that primarily takes single-cp (MAP-of-1) engagements MAY support a higher capacity because those engagements complete faster than traditional multi-CP onboardings. |
| Engagement weighting | A traditional engagement and a single-cp engagement each count as 1 against the MAP's capacity. No reduced weighting is applied to single-cp. The equal-weighting rule protects small MAPs operating at capacity: 1: such a MAP is not expected to take additional engagements simply because some are MAP-of-1s. |
order persistence | A MAP's order is permanent. When a MAP withdraws or its map/status becomes non-ACTIVE, its order is retained for historical audit consistency and is not reassigned. The rotation skips entries whose map/status is not ACTIVE. |
| Designated-position hold time | There is no maximum hold time on the designated-sponsor position. A designated MAP that receives no candidate remains designated until a candidate arrives. The rotation is event-driven — advance occurs on acceptance — so an idle period has no operational cost and no fairness implication. |
candidateRef | active[]/candidateRef is OPTIONAL. When present, it carries a non-identifying reference assigned by the Sponsor MAP for its own coordination — for example a ticket number from the Sponsor's internal onboarding system. The field is free-form and is not standardised across MAPs; consumers outside the issuing MAP MUST NOT rely on its semantics. |
sponsorship.registrationURI | sponsorship.registrationURI is OPTIONAL. When present, it advertises the Sponsor MAP's own onboarding application page (self-service portal, contracted form, or structured registration endpoint), enabling prospective MAPs and the TAG-published sponsor-registration page to deep-link into that MAP's onboarding flow. A MAP that uses non-electronic registration (email, telephone, ticketing) omits the field; prospective MAPs then use the MAP's map/contact[] entries as the contact channel. |
#5.5 Directory Section — Field Reference
| JSON Path | Description | Type | Required |
|---|---|---|---|
list[] | Array of directory lists | Object Array | Yes |
list[]/listType | List type (currently only RCPID) | String | Yes |
list[]/identity[] | Array of CP identity objects | Object Array | Yes |
list[]/identity[]/id | Unique RCPID (UUIDv4) | String | Yes |
list[]/identity[]/name | Full brand name | String | Yes |
list[]/identity[]/processSupport[] | Supported industry processes | Object Array | Yes |
list[]/identity[]/processSupport[]/process | Process name: OTS, GPLB | String | Yes |
list[]/identity[]/processSupport[]/status | Status: ACTIVE, SUSPEND, TEST | String | Yes |
list[]/identity[]/resource[] | CP resources (URLs, contacts) | Object Array | Yes |
list[]/identity[]/resource[]/name | Resource name (e.g., customerAssistURL) | String | Yes |
list[]/identity[]/resource[]/type | Resource type: url, email, phone | String | Yes |
list[]/identity[]/resource[]/value | Resource value | String | Yes |
list[]/identity[]/signing[] | PKI signing configuration | Object Array | Yes |
list[]/identity[]/signing[]/domain | Root domain for public key lookup | String | Yes |
list[]/identity[]/signing[]/routingID[] | Routing IDs signed with this domain (regex) | String Array | Yes |
list[]/identity[]/contact[] | Contact methods for this CP (same structure as map/contact[]) | Object Array | Optional |
list[]/identity[]/contact[]/type | Contact type: phone, email, url | String (enum) | Yes |
list[]/identity[]/contact[]/value | Contact value (number, address, or URL) | String | Yes |
list[]/identity[]/contact[]/purpose[] | What the contact is for: support, sales, technical | String Array | Optional |
list[]/identity[]/contact[]/availabilityFrom | Availability start time | Time (HH:mm) | Optional |
list[]/identity[]/contact[]/availabilityTo | Availability end time | Time (HH:mm) | Optional |
list[]/identity[]/contact[]/availabilityDays[] | Days available | String Array (day names) | Optional |
list[]/identity[]/contact[]/comment | Usage notes | String | Optional |
#5.6 CP Contact Object
Each CP identity MAY include a contact array using the same structure as the MAP contact object. This provides CP-level contact details for operational purposes — typically CP-to-CP sales and support enquiries during the switching process.
#Purpose
The CP contact object allows each CP to advertise its preferred contact methods, trading hours, and escalation points directly in the CP Registry. This eliminates the need for offline spreadsheets or specialised tools — a MAP's application can look up the other CP's preferred contact method in real time and present it to the agent handling the switch.
#Usage Rules
- If
availabilityToandavailabilityFromare absent, the contact SHOULD be interpreted as 24-hour availability - If
availabilityDaysis absent, the contact SHOULD be assumed available 7 days a week - This encourages CPs to be always reachable and promotes optimal behaviour
- The
purposefield allows granular control — a CP can advertise different contact methods forsales,support, andtechnicalenquiries - Contact details are held in the CP Registry and are only accessible via OAuth 2.0-authenticated API calls — they are not publicly accessible and must not be shared with end consumers
#Relationship to MAP Contact Object
| Level | Contact Object | Typical Use |
|---|---|---|
MAP (map/contact[]) | MAP operational contacts | MAP-to-MAP technical issues, delivery failures, infrastructure problems |
CP (list[]/identity[]/contact[]) | CP operational contacts | CP-to-CP sales queries, Switch Match escalations, switch support |
Both use the identical JSON structure (type, value, purpose, availabilityFrom, availabilityTo, availabilityDays, comment), making implementation straightforward — the same parsing code handles both.
#Example Use Case
A CP agent has exhausted all local options trying to resolve a Switch Match failure. Using the MAP's application, the agent looks up the losing CP's contact object from the Master Registry. The application checks the current time and day, filters to the sales purpose, and presents the available contact methods:
- During working hours: phone and email are both available
- Outside working hours: only the email/ticketing URL is shown
- The agent raises a query through the preferred channel with relevant correlation IDs
This process requires no swivel-chair, no MAP involvement, and no specialised tools beyond the agent's normal interface.
#5.7 TOTSCo Directory API
For MAPs that need to consume the TOTSCo directory (to route messages to CPs connected directly to the TOTSCo Hub), the TOTSCo Hub API Specification v2.0 defines the directory endpoint:
| Field | Value |
|---|---|
| API Name | TOTSCo-DirectoryAPI |
| Context | /directory |
| Version | v1 |
| HTTP Method | GET |
| Transport Security | HTTPS, TLS 1.3 |
| Resource | /entry |
Query format: https://{fqdn}/directory/{version}/entry?listType={listType}&identity={identity}
| Parameter | Description | Required |
|---|---|---|
listType | Entity list type (e.g., RCPID) | Yes |
identity | Specific RCPID, all, or omit for all entries | Optional |
This directory can be used to update your local cache when you receive a message from an unknown source ID or encounter an unrecognised signing key.