Skip to content
Connected Services Framework
Part 2 — Implementation
Markdown

#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[], expanded resource[], 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 id is wider in the CSF (UUIDv4 instead of 4-character) but both are valid string values under the JAM Specification (up to 256 characters). The signing[] block is CSF-only; TOTSCo can ignore it. CSF MAPs publish the same processSupport[].process values (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

FieldValue
HTTP MethodGET
AuthenticationOAuth 2.0 Bearer Token (same credentials as messaging)
Response Formatapplication/json
Transport SecurityHTTPS, 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.status is optional. If absent, the MAP is treated as ACTIVE. The valid values are ACTIVE, TEST, and SUSPEND.

#5.4 MAP Section — Field Reference

JSON PathDescriptionTypeRequired
mapContainer for MAP metadataObjectYes
map/nameMAP nameStringYes
map/statusMAP-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/versionLast updated timestamp. Used to determine if local cached values need changingDateTime (yyyy-MM-ddTHH:mm:ssZSSSSS)Yes
map/registrationURIURI for CP/MAP registrationStringOptional
map/serviceStatusURIURI for operational status pageStringOptional
map/serviceStatusServiceURIAPI URI for programmatic status checksStringOptional
map/contact[]Contact methods for the MAPObject ArrayYes
map/contact[]/typeContact type: phone, email, urlString (enum)Yes
map/contact[]/valueContact value (number, address, or URL)StringYes
map/contact[]/purpose[]What the contact is for: support, sales, technicalString ArrayOptional
map/contact[]/availabilityFromAvailability start timeTime (HH:mm)Optional
map/contact[]/availabilityToAvailability end timeTime (HH:mm)Optional
map/contact[]/availabilityDays[]Days availableString Array (day names)Optional
map/contact[]/commentUsage notesStringOptional
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 ArrayYes (empty if no issues)
map/outage[]/priorityITIL 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[]/typeOutage type: PLANNED or INCIDENTString (enum)Yes
map/outage[]/startStart time of the outage or maintenance window (ISO 8601 UTC)DateTimeYes
map/outage[]/endExpected or actual end time (ISO 8601 UTC). Omit for active incidents where resolution time is unknown.DateTimeOptional
map/outage[]/affectedServicesRouting group names (matching map/routingGroups[]/name) affected by this entry. If absent or empty, all services are considered affected.String ArrayOptional
map/outage[]/descriptionHuman-readable status narrative: what is affected, current status, ETA (if known), and any workaround. Updated as the incident evolves.StringYes
map/outage[]/contactEscalation contact specific to this outage. Uses the same structure as a map/contact[] entry.ObjectOptional
map/outage[]/contact/typeContact type: phone, email, or urlString (enum)Yes (if contact present)
map/outage[]/contact/valueContact value (number, address, or URL)StringYes (if contact present)
map/outage[]/contact/commentUsage instructions — e.g. reference numbers, bridge PINs, or when to use this contactStringOptional
map/connectivityConnection detailsObjectYes
map/connectivity/directory/urlDirectory API endpointStringYes
map/connectivity/directory/oauthTokenURLOAuth token endpoint for directory accessStringYes
map/connectivity/services[]Service endpointsObject ArrayYes
map/connectivity/services[]/descriptionHuman-readable descriptionStringYes
map/connectivity/services[]/serviceService type (e.g., messaging)StringYes
map/connectivity/services[]/urlService endpoint URLStringYes
map/connectivity/services[]/oauthTokenURLOAuth token endpoint for this serviceStringYes
map/connectivity/services[]/routingGroups[]Routing groups served by this endpointString ArrayYes
map/connectivity/services[]/relayWhether this endpoint supports message relayBooleanYes
map/routingGroups[]Routing group definitionsObject ArrayYes
map/routingGroups[]/nameRouting group name (matches service routing groups)StringYes
map/routingGroups[]/routingID[]Accepted routing ID patterns (regex)String ArrayYes
map/connections[]Other MAPs this MAP has connectivity withObject ArrayYes
map/connections[]/nameName of the connected MAPStringYes
map/connections[]/registrationURIRegistration URI of the connected MAPStringYes
map/sponsorshipSponsor 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.ObjectYes
map/sponsorship/orderThis 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.IntegerYes
map/sponsorship/nextSponsorThe order number of the MAP designated as the next Sponsor after this MAP. Updated when this MAP accepts a new sponsorship engagement.IntegerYes
map/sponsorship/nextSponsorUpdatedTimestamp when nextSponsor was last updated (ISO 8601 UTC). The MAP with the most recent nextSponsorUpdated is the current active Sponsor.DateTimeYes
map/sponsorship/capacityMaximum 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.IntegerYes
map/sponsorship/registrationURIURI 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.URIOptional
map/sponsorship/active[]Array of currently open sponsorship engagements. Empty array when idle.Object ArrayYes
map/sponsorship/active[]/idUUIDv4 identifier for this engagement, generated by the Sponsor MAP on acceptance.String (UUID)Yes
map/sponsorship/active[]/typeType 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[]/openedTimestamp when the Sponsor MAP accepted the engagement (ISO 8601 UTC).DateTimeYes
map/sponsorship/active[]/candidateRefNon-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.StringOptional

#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.

RuleStatement
Capacity boundsThe 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 weightingA 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 persistenceA 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 timeThere 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.
candidateRefactive[]/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.registrationURIsponsorship.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 PathDescriptionTypeRequired
list[]Array of directory listsObject ArrayYes
list[]/listTypeList type (currently only RCPID)StringYes
list[]/identity[]Array of CP identity objectsObject ArrayYes
list[]/identity[]/idUnique RCPID (UUIDv4)StringYes
list[]/identity[]/nameFull brand nameStringYes
list[]/identity[]/processSupport[]Supported industry processesObject ArrayYes
list[]/identity[]/processSupport[]/processProcess name: OTS, GPLBStringYes
list[]/identity[]/processSupport[]/statusStatus: ACTIVE, SUSPEND, TESTStringYes
list[]/identity[]/resource[]CP resources (URLs, contacts)Object ArrayYes
list[]/identity[]/resource[]/nameResource name (e.g., customerAssistURL)StringYes
list[]/identity[]/resource[]/typeResource type: url, email, phoneStringYes
list[]/identity[]/resource[]/valueResource valueStringYes
list[]/identity[]/signing[]PKI signing configurationObject ArrayYes
list[]/identity[]/signing[]/domainRoot domain for public key lookupStringYes
list[]/identity[]/signing[]/routingID[]Routing IDs signed with this domain (regex)String ArrayYes
list[]/identity[]/contact[]Contact methods for this CP (same structure as map/contact[])Object ArrayOptional
list[]/identity[]/contact[]/typeContact type: phone, email, urlString (enum)Yes
list[]/identity[]/contact[]/valueContact value (number, address, or URL)StringYes
list[]/identity[]/contact[]/purpose[]What the contact is for: support, sales, technicalString ArrayOptional
list[]/identity[]/contact[]/availabilityFromAvailability start timeTime (HH:mm)Optional
list[]/identity[]/contact[]/availabilityToAvailability end timeTime (HH:mm)Optional
list[]/identity[]/contact[]/availabilityDays[]Days availableString Array (day names)Optional
list[]/identity[]/contact[]/commentUsage notesStringOptional

#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 availabilityTo and availabilityFrom are absent, the contact SHOULD be interpreted as 24-hour availability
  • If availabilityDays is absent, the contact SHOULD be assumed available 7 days a week
  • This encourages CPs to be always reachable and promotes optimal behaviour
  • The purpose field allows granular control — a CP can advertise different contact methods for sales, support, and technical enquiries
  • 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

LevelContact ObjectTypical Use
MAP (map/contact[])MAP operational contactsMAP-to-MAP technical issues, delivery failures, infrastructure problems
CP (list[]/identity[]/contact[])CP operational contactsCP-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:

FieldValue
API NameTOTSCo-DirectoryAPI
Context/directory
Versionv1
HTTP MethodGET
Transport SecurityHTTPS, TLS 1.3
Resource/entry

Query format: https://{fqdn}/directory/{version}/entry?listType={listType}&identity={identity}

ParameterDescriptionRequired
listTypeEntity list type (e.g., RCPID)Yes
identitySpecific RCPID, all, or omit for all entriesOptional

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.