#4. Message API (Letterbox)
Connected Services Framework (CSF) — Part 2: Implementation — Version 2.0
#4.1 Overview
The CSF defines a REST API — the Letterbox API — for sending and receiving messages between MAPs. This API is compatible with the TOTSCo Hub letterbox interface as defined in the TOTSCo Hub API Specification v2.0.
The Letterbox API is synchronous in its HTTP interaction: when a message is sent, the API replies within the same session. However, the messaging model is asynchronous — the HTTP 202 response acknowledges receipt, not processing completion.
#4.2 API Specification
| Field | Value |
|---|---|
| API Name | CSF-LetterboxAPI |
| HTTP Method | POST |
| URI Pattern | https://{fqdn}/letterbox/{version}/post |
| Transport Security | HTTPS, TLS 1.3 |
| Authentication | OAuth 2.0 Bearer Token |
| Request Format | application/json |
| Request Headers | Authorization, Accept, Content-Type, X-CSF-SIGNATURE, X-CSF-SIGNATURE-DATESTAMP |
#URI Elements
| Element | Description | Format |
|---|---|---|
fqdn | Fully Qualified Domain Name of the MAP's letterbox endpoint | RFC 1035 compliant |
version | API version number | n.n (e.g., 1.0) |
#4.3 Message Structure
Every message consists of two parts:
- Envelope — Delivery instructions (source, destination, routing, audit data). Defined by the JAM Specification.
- Message Body — The industry process content (e.g., SforB Switch Match). Defined by the relevant process specification.
The CSF is responsible only for the envelope. The message body is opaque to the transport layer unless the MAP provides a hosted solution (portal, matching service) on behalf of its CPs.
#Envelope Example
The following JSON example uses the structure from the JAM Specification. Each auditData entry is a { "name": "<String>", "value": "<String>" } pair — both fields are strings and use double-quoted keys consistently. All JSON examples in this document have been audited for that consistency.
{
"envelope": {
"source": {
"type": "RCPID",
"identity": "d8322d80-92c8-4906-a408-f1d2daf7e03d",
"correlationID": "XYZ987"
},
"destination": {
"type": "RCPID",
"identity": "7d87c2a7-4f64-433c-8ae1-9460f19a0a13",
"correlationID": "ABC123"
},
"routingID": "businessSwitchMatchRequest",
"auditData": [
{
"name": "auditFieldName",
"value": "auditFieldValue"
}
]
},
"_messageBody": {
"_comment": "Process-specific message content goes here"
}
}
#Envelope Elements
| Element | Description | Type | Required |
|---|---|---|---|
envelope | Container for delivery information | Object | Yes |
source | Originator of the message and return address | Object | Yes |
destination | Intended recipient of the message | Object | Yes |
source/type, destination/type | The directory list type (e.g., RCPID) | String | Yes |
source/identity, destination/identity | The RCPID of the sender/recipient | String | Yes |
source/correlationID | Unique identifier for request correlation. MUST always be provided in source. | String | Yes |
destination/correlationID | Present only in response messages — contains the original sender's correlationID | String | Yes in Response/Failure messages |
routingID | Determines message type and routing. Must be a value supported by the recipient MAP's routing groups. | String | Yes |
auditData | Name-value pairs for reporting and auditing | Array | Optional |
auditData[]/name | The audit field name | String | Yes (within array) |
auditData[]/value | The audit field value | String | Yes (within array) |
Note: The actual message body eelement name MUST match the
routingID(e.g.,businessSwitchMatchRequest). The_messageBodykey shown above is illustrative.
#Audit Data Conventions
Two CSF-wide uses for auditData shape how the field SHOULD be populated by senders and read by receivers:
- Test traffic flagging. Messages originated by, or routed on behalf of, a
TEST-status CP orTEST-status MAP MUST include{ "name": "test", "value": "true" }. The receiving MAP MUST preserve this flag end-to-end and MUST exclude such messages from production telemetry. The full rules — including the MAP-overrides-CP behaviour and the per-message exclusion criteria — are defined in Onboarding & Testing Process §5 — Test Traffic Flagging in the JAM Envelope and §5.1 — Telemetry Exclusion. - Match-session sequencing for telemetry. A SforB Switch Match journey often spans several
businessSwitchMatchRequestattempts as the GRCP corrects its data. Session and attempt metadata SHOULD be carried inauditDataso multi-attempt journeys can be aggregated as a single customer journey when MAPs compute industry KPIs, rather than each attempt inflating the failure rate. The recommendedmatch*/serviceMatch*/lrcp*/map*field-name prefixes and the three-layer GRCP/LRCP/MAP scheme are summarised in Onboarding & Testing Process §5, with the full proposal in REVIEW.md §6.5 — Match Session Sequencing.
#4.4 Successful Response
On successful validation and acceptance, the API returns:
HTTP 202 Accepted
"The request has been accepted for processing, but the processing has not been completed. The request might or might not be eventually acted upon and may be disallowed when processing occurs."
No JSON body is returned on a successful 202 response.
#Validation Performed Before 202
The receiving MAP validates the following:
- OAuth 2.0 token validity
- JSON message structure
- Envelope attribute validity
- Source type and RCPID validity
- Source RCPID account status
- Destination type and RCPID validity
- Destination RCPID account status
- Source MAP authorisation via
_mapkeyDNS verification — see §4.4.1 below. - Routing ID validity
- DKIM signature verification (via
X-CSF-SIGNATURE)
The MAP could perform these in any order, but the MAP MUST NOT respond until all checks have passed.
#4.4.1 Per-Message Source-MAP Verification (Step 8 detailed)
For every inbound message, in addition to the DKIM signature verification defined in §3.4, the receiving MAP MUST verify that the originating MAP is the one currently authorised by the source CP in DNS:
- The receiving MAP MUST resolve
[sourceRCPID]._mapkey.[source-cp-domain]in DNS, wheresourceRCPIDis taken fromenvelope.source.identityandsource-cp-domainis the signing domain from the DKIMd=tag in theX-CSF-SIGNATUREheader. - The receiving MAP MUST extract the originating MAP's letterbox URL — for direct CSF-MAP-to-CSF-MAP traffic this is the MAP that established the TLS connection and presented the OAuth bearer token; for HMAP-forwarded traffic, it is the first non-HMAP entry in the
X_CSF_ROUTEheader chain (see §4.4.2 below). - If the resolved
_mapkeyURL does not match the originating MAP's letterbox URL (compared on host + path, case-insensitive on host, case-sensitive on path, trailing-slash-insensitive), the receiving MAP MUST reject the message with HTTP 403 and CSF error code9301("DNS_mapkeydoes not resolve to the originating MAP"). The asynchronous delivery-failure record MUST include the resolved_mapkeyvalue (for diagnostics). - DNS resolutions performed for
_domainkey(§3.4) and_mapkey(this section) MUST honour the published TTL and SHOULD be served from the MAP's local DNS server per §3.3 — MAP Responsibilities (RFC 6303).
Note on
9301scope. Error code9301was previously documented as the reject forrcpidStatusRequestauthorisation failures only (§7.2.3). As of CSF v2.x it covers all source-MAP authorisation failures, including this per-message verification. Existing §7.2.3 usage is unchanged and is a specialisation of this general rule.
Note on TOTSCo response policy. The
9301code is a CSF-specific failure. When operating as a Hub MAP (HMAP), TOTSCo does not implement DKIM signing (§3.6) and does not perform this check; CSF MAPs MUST NOT include9301in responses sent back to TOTSCo Hub traffic (§4.6). This is consistent with how CSF 8xxx codes are excluded from TOTSCo responses.
#4.4.2 HMAP-Forwarded Traffic
When a message arrives from an HMAP (e.g. TOTSCo), the originating MAP for the purposes of §4.4.1 is the first non-HMAP entry in the X_CSF_ROUTE header chain, not the HMAP itself. HMAPs do not implement DKIM signing (§3.6) and do not appear in any CP's _mapkey record — the _mapkey check therefore does not apply to the HMAP. It applies only to the underlying CSF MAP that originated the message into the HMAP.
For HMAP-only traffic where no CSF MAP appears in the route (i.e. a message originated entirely within a hub-and-spoke network), §4.4.1 is not enforced; the integrity guarantee for such traffic rests on the HMAP's own commercial controls and existing TOTSCo authentication. The boundary between HMAP-only and CSF-originated traffic is determined by the presence or absence of CSF MAP entries in X_CSF_ROUTE.
Trust assumption for
X_CSF_ROUTE. The chain is treated as trustworthy on the basis of (a) the OAuth-authenticated channel each hop uses and (b) the bilateral trust model between MAPs that have completed onboarding. Signing ofX_CSF_ROUTEentries by each MAP that appends to the chain is a future enhancement and not required for this clause.
#4.5 Synchronous Error Responses
#HTTP Error Codes
| HTTP Code | Error | Description |
|---|---|---|
| 400 | Bad Request | Invalid JSON, missing required fields, or validation error |
| 401 | Unauthorised | Authentication failure (invalid OAuth token or API key) |
| 403 | Forbidden | Valid credentials but insufficient authorisation, or PKI verification failure |
| 404 | Not Found | Requested resource not found |
| 405 | Method Not Allowed | HTTP method not supported (e.g., GET instead of POST) |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Unexpected processing error |
| 501 | Not Implemented | HTTP method not supported by the server |
| 502 | Bad Gateway | Invalid response from upstream |
| 503 | Service Unavailable | Temporary maintenance |
| 504 | Gateway Timeout | Upstream response timeout |
#Validation Error Codes (9xxx Series)
These codes are returned in the JSON error response body alongside the appropriate HTTP status code:
| Step | Validation | HTTP Code | Error Code |
|---|---|---|---|
| 1 | OAuth 2.0 token validation | 401 | N/A |
| 2 | JSON message structure | 400 | N/A |
| 3 | Envelope attribute validation | 400 | N/A |
| 4 | Source type validity | 400 | 9002 |
| 5 | Source RCPID validity | 400 | 9003 |
| 6 | Source RCPID account status | 403 | 9003 |
| 7 | Destination type validity | 400 | 9000 |
| 8 | Destination RCPID validity | 400 | 9001 |
| 9 | Destination RCPID account status | 403 | 9001 |
| 10 | Source MAP authorisation via _mapkey DNS check (§4.4.1) | 403 | 9301 |
| 11 | Routing ID mapping to source RCP | 400 | 9010 |
| 12 | Routing ID validity | 400 | 9012 |
#PKI Error Codes (8xxx Series)
| Error Code | Meaning | Retry? |
|---|---|---|
| 8101 | PERM_FAIL — Signature verification failed permanently | No |
| 8102 | TEMP_FAIL — Signature verification failed due to temporary issue (e.g., DNS) | Yes |
#Synchronous Error Codes (9xxx Series)
| Error Code | Meaning |
|---|---|
| 9000 | Unknown or invalid destination Type. |
| 9001 | Unknown or invalid destination ID. |
| 9002 | Unknown or invalid source Type. |
| 9003 | Unknown or invalid source ID. |
| 9301 | DNS _mapkey does not resolve to the originating MAP — source-MAP authorisation failed (§4.4.1). Also used for rcpidStatusRequest authorisation failures (§7.2.3). Not sent to TOTSCo Hub traffic. |
#Error Response Format
For validation errors:
{
"errorCode": "9002",
"errorText": "Unknown or invalid source Type."
}
For HTTP-level errors:
{
"code": "400",
"message": "Bad Request",
"description": "Schema validation failed in the Request: [Path '/directory'] Object has missing required properties ([\"listID\"])"
}
The original documentation uses two different error response formats — one with errorCode/errorText for validation errors (9xxx,8xxx) and another with code/message/description for HTTP errors. Implementers should support both formats.
#4.6 Asynchronous Error Handling — Message Delivery Failures
When a MAP is unable to deliver a message to its intended recipient, it creates a failure notification message back to the originator using the following format:
{
"envelope": {
"source": {
"type": "RCPID",
"identity": "MAP-X-IDENTITY"
},
"destination": {
"type": "RCPID",
"identity": "ORIGINAL-SOURCE-RCPID",
"correlationID": "ORIGINAL-CORRELATION-ID"
},
"routingID": "messageDeliveryFailure",
"auditData": [
{ "name": "originalDestinationType", "value": "RCPID" },
{ "name": "originalDestination", "value": "DESTINATION-RCPID" },
{ "name": "originalRoutingID", "value": "businessSwitchMatchRequest" },
{ "name": "faultCode", "value": "9008" }
]
},
"messageDeliveryFailure": {
"code": "9008",
"text": "Unable to deliver the message to the destination, timed out.",
"severity": "failure"
}
}
#Delivery Failure Fields
| Field | Description | Type |
|---|---|---|
messageDeliveryFailure | Container for delivery failure details | Object |
code | Numeric fault code for remedial action | Integer |
text | Human-readable description of the fault | String |
severity | Nature of the notification: information, warning, or failure | String |
Note: The source does not contain a
correlationIDbecause delivery failure notifications cannot be replied to. The originator's correlation ID is returned in the destination field.
#Delivery Failure Codes (9xxx Series)
| Code | Description | Severity |
|---|---|---|
| 9005 | Unable to deliver — no valid route | Failure |
| 9006 | Unable to deliver — rejected, invalid message format (HTTP 400 from destination) | Failure |
| 9007 | Recipient rejected message (HTTP 404 from destination) | Failure |
| 9008 | Unable to deliver — timed out (HTTP 5xx or timeout after retries) | Failure |
| 9013 | Unable to deliver — invalid API key | Failure |
| 9014 | Unable to deliver — API key expired | Failure |
| 9015 | Unable to deliver — digital certificate invalid | Failure |
| 9016 | Unable to deliver — digital certificate expired | Failure |
The CSF-specific 8xxx series is complete with 8101 (PERM_FAIL) and 8102 (TEMP_FAIL) — no further CSF 8xxx delivery failure codes are required (agreed by the TAG). When responding to TOTSCo, CSF-specific 8xxx codes MUST NOT be included in the response, since TOTSCo does not recognise them and only the industry-standard 9xxx series applies on that interface.
| Error Code | Meaning | Retry? |
|---|---|---|
| 8101 | PERM_FAIL — Signature verification failed permanently | No |
| 8102 | TEMP_FAIL — Signature verification failed due to temporary issue (e.g., DNS) | Yes |
#4.7 Message Delivery Policy
This section ratifies the CSF's message delivery policy for MAP-to-MAP exchanges. It also marks the boundary at which TOTSCo's hub-side principles (the OTA2 "GPLB Switching for Business Message Delivery Principles") bind on the TOTSCo side — see §4.7.4.
The policy was formalised by the TAG following the comparative analysis against the OTA2 principles document (see the CSF-vs-GPLB-MDP Analysis Report). The architectural framing in §4.7.1 below explains why CSF retains its own P2P delivery policy rather than adopting the GPLB hub schedules wholesale.
#4.7.1 Architectural framing — synchronous P2P vs hub boundary
The CSF is a peer-to-peer synchronous transport. Two MAPs exchange JAM messages directly over TLS 1.3 / OAuth 2.0 / DKIM-signed HTTP. The sending MAP knows the result of its delivery attempt in the same HTTP exchange:
HTTP 202confirms the receiving MAP has accepted the message for downstream processing.HTTP 4xxplus a synchronous9xxxindustry error code confirms the receiving MAP has rejected it for a documented reason.- A network / connection / response timeout signals that the receiving MAP is unreachable.
This is materially different from a centralised hub model — where the hub sits between sender and destination and absorbs the delivery asynchrony, queueing, retrying on a per-protocol schedule, ageing stale queue items, and emitting asynchronous failure notifications back to the sender on a separate channel. CSF MAPs do none of that at the framework layer. The synchronous response IS the delivery indication for the transport step.
The asymmetry has one important boundary: the HMAP interface between a CSF MAP and TOTSCo. TOTSCo is a hub by its own design, so traffic crossing that boundary follows the GPLB Message Delivery Principles on TOTSCo's side. See §4.7.4 below.
#4.7.2 Required behaviours (CSF MAP-to-MAP)
When a sending MAP attempts to deliver a JAM message to a receiving MAP's letterbox, the following are required:
- Connection timeout: 1 second. TCP / TLS handshake to the destination MUST succeed within 1 second or the attempt is treated as a failed delivery and feeds into the retry loop.
- Response timeout: 3 seconds. Once connected, the sender waits 3 seconds for
HTTP 202. If no response is received within 3 seconds, the attempt is treated as a failed delivery and feeds into the retry loop. - Retry with exponential backoff and jitter. See §4.7.5 for the architectural rationale. MAPs MUST NOT use linear or fixed-cadence retry schedules at the CSF P2P boundary.
- Respect HTTP semantics. MAPs MUST honour
503 Service Unavailableand429 Too Many Requestswith the destination'sRetry-Afterheader. ARetry-Aftervalue overrides the sender's normal backoff schedule for that attempt. - Circuit-breaker pattern. MAPs MUST implement a circuit-breaker: after N consecutive failures to the same destination (recommended
N = 5, tunable per MAP), the sender SHOULD stop active retry, fail subsequent messages fast for a recovery period, and raise an operational alert. - Bounded maximum retry period. MAPs MUST NOT "wait forever". The framework recommends a maximum retry window in the order of hours, not days — typically 1–4 hours for production traffic. Persistent failure beyond the window triggers the escalation in §4.7.3.
- All delivery attempts MUST be logged. Log entries include the destination URL, timestamp, attempt number, outcome, and any synchronous error code returned. Audit retention follows §9 audit-log requirements.
#4.7.3 Operational-engagement escalation
When a destination MAP fails persistently beyond the sender's retry window, the CSF's primary recovery mechanism is operational engagement between MAP operations teams — not silent retry. The procedure:
- The failing MAP publishes a
map/outage[]entry in its CP Registry (per §5.4 Field Reference). Every other MAP picks it up on the next directory poll and applies the appropriate downgrade to retry behaviour (see Part 2 §9.3 Outage Notification). - The sender raises an internal alert when its retry window is exhausted. The alert records the destination RCPID, the duration of consecutive failure, and the last synchronous error code observed.
- Operations teams from both sides engage via the published
map/contact[]channels (phone, email, ticketing URL — see §5.6 CP Contact Object). Contact entries include availability hours, days, and purpose tags so the sender knows which channel is appropriate at the time of the incident. - The framework's standard incident response process applies — see Part 1 §10.6.5 Incident Response Coordination.
This sequence is the deliberate replacement for "wait 12 days" hub-side queue ageing. CSF MAPs do not silently retry for days; they escalate. A persistent destination failure is a P1 / P2 incident, not a routine retry condition.
#4.7.4 The HMAP boundary
When a CSF MAP exchanges traffic with TOTSCo via the dedicated HMAP interface, the boundary inherits hub behaviour by definition. TOTSCo is a centralised hub, and the GPLB Switching for Business Message Delivery Principles V0.2 (OTA2) bind TOTSCo's hub-side delivery to the destination RCP. Specifically:
- The 1 s connection / 3 s response timeouts in §4.7.2 are aligned with the GPLB principles — no asymmetry on those two values.
- TOTSCo applies its own hub-side retry schedules per the GPLB principles — linear 5 / 10 / 15 / 20 / 25 s for Match Requests within a 30 s window; 10 / 20 / 30 / 60 s then a 60 s fixed-cadence tail for other SforB messages within a 12-day window.
- TOTSCo emits asynchronous failure notifications back to the sender's pre-configured failure endpoint when hub-side delivery fails. The CSF MAP receives those notifications as a standard hub client.
- TOTSCo operates the single-flight-per-endpoint queue and queue ageing on its hub side.
The HMAP interface is locked down per §6.3.1. CSF version increments — including any future change to §4.7 itself — do NOT propagate through the dedicated TOTSCo endpoints. The asymmetric behaviour at the HMAP boundary is stable and bilateral by design, so the GPLB hub schedules do not bleed back into the wider CSF P2P transport.
For the full operating model — including the per-MAP dedicated endpoint pattern, the worked NowYoYo / Red Company bidirectional example, and the locked-down interface architectural commitment — see the standalone TOTSCo HMAP Integration Guide.
#4.7.5 Why exponential backoff (and not linear / fixed-cadence retry)
The CSF retains exponential backoff with jitter as its P2P default. The linear and fixed-cadence retry schedules in the OTA2 GPLB Message Delivery Principles (5 / 10 / 15 / 20 / 25 s for Match Requests; 10 / 20 / 30 / 60 s then a 60 s cadence for other messages) are architecturally appropriate for a single centralised hub but inappropriate for a distributed P2P network for two distinct reasons:
Linear schedules synchronise retry storms. When a destination is recovering, many CSF MAPs hitting it at exactly the same intervals creates the "thundering herd" effect that exponential-with-jitter is specifically designed to prevent. Under linear retry pressure, the destination has a synchronised load spike every 5 s (Match) or every 60 s (other) until it falls back over. Under exponential-with-jitter pressure, the load is spread across the recovery window and the destination is far more likely to recover cleanly.
Exponential-with-jitter is the industry-standard distributed-systems pattern. Every modern cloud SDK (AWS, Google, Microsoft, Cloudflare, etc.) uses this pattern as its default retry policy. The pattern is documented in the SRE literature and is the recommended approach for any service operating across multiple independent clients. The CSF aligns with that practice; the alignment is deliberate, not incidental.
The framework's principle is therefore: exponential backoff with jitter, bounded by a maximum retry window, with operational escalation taking over beyond that window. Specific exponential parameters (base, cap, jitter range) are MAP implementation choices; the SHAPE of the curve — geometric growth with randomisation — is required.
#What exponential backoff with jitter means in practice
- Exponential backoff: when a request fails, wait a short period, then exponentially grow the delay between subsequent retries (e.g. 1 s, 2 s, 4 s, 8 s, 16 s, capped at some maximum). This gives the struggling destination time to recover instead of repeatedly hammering it.
- Jitter: a random component (typically ±25 %) added to each delay, so independent clients spread their retries across the recovery window rather than aligning on identical intervals.
This is the only retry shape that scales gracefully as the CSF network grows. Linear retries do not.
#4.7.6 Summary of bindings
| Rule | CSF MAP-to-MAP (P2P) | HMAP boundary (CSF MAP ↔ TOTSCo) |
|---|---|---|
| 1 s connection timeout | MUST | MUST (aligned with GPLB) |
| 3 s response timeout | MUST | MUST (aligned with GPLB) |
| Exponential backoff with jitter | MUST | TOTSCo applies GPLB linear / fixed-cadence schedule on its hub side; CSF MAP applies exponential on its own retries to TOTSCo |
Respect Retry-After on 429 / 503 | MUST | MUST |
| Circuit-breaker | MUST | MUST (CSF MAP side); n/a on hub side |
| Maximum retry period | MUST be bounded (hours) | TOTSCo applies the GPLB 30 s Match / 12-day other-message holds |
| Failure notification to sender | Synchronous — 9xxx / 8xxx in the original response | Asynchronous from TOTSCo when hub-side delivery fails; CSF MAP consumes as a standard hub client |
Operational escalation via map/contact[] / map/outage[] | Primary recovery mechanism beyond the retry window | Applies on the CSF MAP side; TOTSCo follows its own incident process |
| Logging of every delivery attempt | MUST | MUST |
For test cases that exercise these behaviours, see Onboarding & Testing Process §8.7 Retry & Delivery Policy.