CSA Integration
1. Overview
The Event Registration workflow integrates with Cycling South Africa (CSA) to validate rider membership and racing licenses. This validation is performed via three sequential FormField types during the registration question workflow:
| StepCode | FormField Class | Purpose |
|---|---|---|
CSI |
|
Capture CSA membership number and identification details |
CSM |
|
Validate CSA membership status via CSA external API |
CSL |
|
Validate CSA racing license via CSA external API |
These steps form a dependent chain: CSI captures the identifier, CSM validates membership using that identifier, and CSL validates the racing license.
2. ProcessData Flow
The CSA steps follow the standard ProcessData staging pattern. Each step reads data from prior steps and stores its result:
| Step | Reads From | Stores |
|---|---|---|
CSI |
(user input) |
CSA membership number, ID number, ID country |
CSM |
CSI ProcessData (CSA number or ID number) |
Membership validation result, CSA member ID, membership status |
CSL |
CSI ProcessData (CSA number or ID number) |
License validation result, membership status, license status |
Both CSM and CSL gate form progression — the participant cannot advance to the next question if validation fails, unless an alternative action is chosen (day license, defer, exempt).
3. CSA API
The CSA API is hosted by Cycling South Africa at cyclingsa-events.co.za. It provides two endpoints for checking a rider’s membership and racing license status.
Official API documentation: https://documenter.getpostman.com/view/28907025/2sAYdkFoEm
3.1. Authentication
Authentication is via an API key passed in the JSON request body (not as an HTTP header).
| Parameter | Description |
|---|---|
|
A unique API key assigned to the organisation. Must be included in every request body. |
The API key must be stored as an application property (csa.api.key), never hardcoded.
3.3. Endpoints
3.3.1. Check Membership Status
Checks whether a rider has an active CSA membership on a given date.
URL: ?c=Member&fn=checkMembershipStatus
Method: POST
Content-Type: application/json
Request body:
{
"key": "<API_KEY>",
"idNumber": "8501015009087",
"date": "2026-06-15"
}
| Field | Type | Description |
|---|---|---|
|
String (required) |
API authentication key |
|
String (conditional) |
South African ID number. Much better suited for ZA citizens as it is a unique national identifier. Cannot be used for foreign riders. |
|
String (conditional) |
CSA’s internal member reference number. Can be used for any rider (local or foreign), but |
|
String (optional) |
Date to check in |
Either idNumber or csaId must be provided. When the person’s ID country is ZA and an ID number is available, prefer idNumber as it is the stronger identifier. For foreign riders, csaId is the only option.
|
Response body:
{
"ok": true,
"memberFound": true,
"active": true,
"date": "2026-06-15"
}
| Field | Type | Description |
|---|---|---|
|
Boolean |
|
|
String |
Error description. Only present when |
|
Boolean |
|
|
Boolean |
|
|
String |
The date that was checked (YYYY-MM-DD). |
3.3.2. Check Racing License Status
Checks the rider’s membership status AND racing license status in a single call. This is the more comprehensive endpoint.
URL: ?c=Member&fn=checkRacingLicenceStatus
Method: POST
Content-Type: application/json
Request body: Same structure as membership check.
{
"key": "<API_KEY>",
"idNumber": "8501015009087",
"date": "2026-06-15"
}
Response body:
{
"ok": true,
"memberFound": true,
"memberCsaId": "12345",
"membershipStatus": "ACTIVE",
"licenceStatus": "ACTIVE",
"date": "2026-06-15"
}
| Field | Type | Description |
|---|---|---|
|
Boolean |
|
|
String |
Error description. Only present when |
|
Boolean |
|
|
String |
The CSA ID of the cyclist (returned by CSA, useful for cross-referencing). |
|
String |
Membership status on the given date (see status codes below). |
|
String |
Racing license status on the given date (see status codes below). |
|
String |
The date that was checked (YYYY-MM-DD). |
3.4. Status Codes
3.4.1. Membership Status Values
| Value | Meaning |
|---|---|
|
The cyclist has an active membership on the checked date. |
|
The cyclist’s membership has expired. |
|
The cyclist’s membership is suspended. |
|
The cyclist is not a CSA member. |
|
The cyclist is not a CSA member (alternative phrasing of |
3.4.2. License Status Values
| Value | Meaning |
|---|---|
|
The cyclist has an active racing license on the checked date. |
|
The license is inactive. This occurs when the cyclist has a racing license but their membership has expired — the license becomes inactive because it depends on active membership. |
|
The cyclist’s racing license has been suspended. |
|
The cyclist does not have a racing license. |
|
The cyclist does not have a racing license (alternative phrasing of |
|
The |
3.5. Key API Observation
The checkRacingLicenceStatus endpoint returns both membership and license status in a single response. The legacy implementation exploits this by calling only checkRacingLicenceStatus for both the membership check and the license check. This avoids making two separate API calls.
The new Java implementation should follow the same pattern: use checkRacingLicenceStatus as the primary endpoint, and only fall back to checkMembershipStatus if license information is not needed.
4. Java Implementation: CsaApiClient
The CsaApiClient Spring service in admin-service must implement the real API integration. The current implementation uses mock responses (csa.api.mock=true).
4.1. Configuration Properties
| Property | Default | Description |
|---|---|---|
|
CSA API base URL |
|
|
(none — required) |
API authentication key |
|
|
HTTP request timeout in milliseconds |
|
|
Use mock responses for development/testing |
4.2. Implementation Notes
-
HTTP client: Use Spring
RestTemplateorWebClientto POST JSON to the CSA API. -
Request construction: Build a JSON object with
key,idNumberorcsaId, anddate. -
SSL: The legacy implementation disables SSL verification. The new implementation must NOT disable SSL verification. If certificate issues arise, add the CSA certificate to the Java truststore.
-
Error handling:
-
cURL/HTTP errors → return
CsaMembershipResponse.offline()orCsaLicenseResponse.offline() -
ok=falsein response → log the error message, return offline status -
memberFound=false→ returnNOT_FOUNDstatus
-
-
Lookup preference: When the person’s ID country is
ZAand an ID number is available, sendidNumber(stronger identifier). For foreign riders, sendcsaId(the only option). Both can be used for any rider, butidNumberis preferred for ZA citizens.
4.3. Suggested CsaApiClient Method Signatures
/**
* Check membership status using the checkMembershipStatus endpoint.
* Use when only membership status is needed.
*/
public CsaMembershipResponse checkMembership(String idNumber, String csaId, LocalDate date);
/**
* Check both membership and license status using checkRacingLicenceStatus.
* Preferred method -- returns both statuses in one call.
*/
public CsaLicenseResponse checkLicense(String idNumber, String csaId, LocalDate date);
5. Validation Workflow
5.1. Business Rules Hierarchy
CSA requirements are determined at two levels, with category overriding event:
5.1.1. Rule: License Requires Membership
When a racing license is required, CSA membership is always implicitly required. A rider cannot hold an active license without an active membership (CSA enforces this — an expired membership causes the license status to become INACTIVE).
5.1.2. Rule: Requirement Inheritance
Requirements cascade: Series → Event → Category. A category-level setting overrides the event-level setting. An event-level setting overrides the series-level setting. If no value is set at a level, it inherits from the parent.
5.1.3. Rule: Strict vs Lenient Registration
-
Strict (
strictRegistration=true): The rider must have valid membership/license at the time of registration. The form blocks progression if validation fails. -
Lenient (
strictRegistration=false): The rider can register even without valid status. The compliance state is recorded and can be enforced closer to event day.
5.2. Membership Validation (CSM)
The CSM step calls the CSA API to check whether the rider’s membership is current and valid for the event.
Validation logic:
-
Read the CSA ID (or ID number) from the CSI step’s ProcessData
-
Call
checkRacingLicenceStatuswith the ID and the event start date as the check date -
Map the
membershipStatusresponse to an internal status code -
Determine available actions based on the status
Status mapping:
CSA API membershipStatus |
Internal Status | Available Actions |
|---|---|---|
|
|
Proceed (auto-advance) |
|
|
Renew membership, purchase day license (if allowed), defer, remove person |
|
|
Remove person from registration |
|
|
Purchase day license (if allowed), defer, remove person |
(API unreachable) |
|
Defer (allow registration, check later) |
When status is ACTIVE: The CSM step auto-fills the answer as valid and can be configured to auto-advance (skip=true) so the user sees no interruption.
5.3. License Validation (CSL)
The CSL step calls the CSA API to check the rider’s racing license status.
Validation logic:
-
Read the CSA ID from CSI ProcessData (same as CSM)
-
Call
checkRacingLicenceStatuswith the ID and the event start date -
Map the
licenceStatusresponse to an internal status code -
Determine available actions based on the status
Status mapping:
CSA API licenceStatus |
Internal Status | Available Actions |
|---|---|---|
|
|
Proceed (auto-advance) |
|
|
Renew membership first (license becomes active when membership is renewed), defer, remove person |
|
|
Remove person from registration |
|
|
Purchase day license (if allowed), defer, remove person |
(API unreachable) |
|
Defer (allow registration, check later) |
|
The |
5.4. User Actions on Validation Failure
When CSM or CSL validation fails, the user is presented with action options:
| Action | Description |
|---|---|
PROCEED |
Only available when status is |
RENEW |
Directs the rider to renew their CSA membership. The rider must renew externally and then retry. |
DAY_LICENSE |
Adds a day license to the order. Only available if the event allows day licenses. A day license covers the rider for the specific event date only. |
DEFER |
Allows the registration to continue with a compliance status of |
REMOVE |
Removes this person from the registration. Used when the rider cannot or will not resolve the compliance issue. |
6. Caching Strategy
The legacy implementation uses a caching strategy to avoid redundant API calls:
-
Active status: Cache the result until the event start date. An active membership/license checked for the event date will not change, so the result is stable.
-
Non-active status: Cache for 5 minutes only. The rider may renew their membership at any time, so we retry after a short interval.
-
Within 1 minute: Always return the cached result to prevent rapid repeated calls (e.g., page refreshes).
The new implementation should use Spring’s @Cacheable with a TTL-based eviction policy. Cache keys should include the CSA ID and the check date.
7. Admin-Service Wrapper API
The admin-service exposes its own REST endpoints that wrap the CSA API. Frontends (registration-portal, admin-ui) call the admin-service endpoints instead of calling CSA directly. This avoids CORS issues and keeps the API key server-side.
7.1. Endpoint: Check Membership and License
POST /api/csa/check
Request body:
{
"idNumber": "8501015009087",
"csaId": "12345",
"date": "2026-06-15"
}
| Field | Type | Description |
|---|---|---|
|
String (optional) |
South African ID number. Preferred for ZA citizens. |
|
String (optional) |
CSA member ID. Used for foreign riders or when ID number is unavailable. |
|
String (optional) |
Date to check (YYYY-MM-DD). Defaults to current date if omitted. |
At least one of idNumber or csaId must be provided.
|
Response body:
{
"ok": true,
"memberFound": true,
"memberCsaId": "12345",
"membershipStatus": "ACTIVE",
"licenceStatus": "ACTIVE",
"date": "2026-06-15"
}
The response DTO mirrors the CSA checkRacingLicenceStatus response. For now the admin-service wrapper shares the same DTO structure as the upstream CSA API, keeping the integration simple. If the wrapper needs to diverge (e.g., enriching with internal compliance data), the DTOs can be separated later.
7.2. Endpoint: Check Membership Only
POST /api/csa/check-membership
Same request body. Returns the simpler membership-only response (mirrors checkMembershipStatus).
{
"ok": true,
"memberFound": true,
"active": true,
"date": "2026-06-15"
}
7.3. Design Rationale
-
No CORS: Frontends call admin-service on the same origin. Admin-service calls CSA server-side.
-
API key security: The CSA API key is never exposed to the browser.
-
Caching: Admin-service can cache CSA responses (see Caching Strategy) to reduce external API load.
-
Resilience: Admin-service handles timeouts, retries, and circuit breaking. Frontends get a consistent error response regardless of CSA API behaviour.
-
Shared DTOs (for now): The admin-service response mirrors the CSA API response structure. This avoids unnecessary mapping layers while the integration is straightforward. If admin-service needs to enrich or transform the response in future, the DTOs can be separated without breaking the upstream CSA contract.
8. ComplianceStatus on EventParticipant
The EventParticipant entity has a complianceStatus field (enum ComplianceStatus) that records the outcome of CSA validation:
| Value | Meaning |
|---|---|
|
Membership and license checks passed with |
|
The rider is exempt from CSA requirements (manually set by admin). |
|
The rider purchased a day license for this event. |
|
The rider deferred resolution. Must be resolved before event day. |
|
Checks have not yet been completed (initial state). |
This field is set by the CSM and CSL FormFields when saving their ProcessData, and is also available for onDone() to read when creating the final EventParticipant entity.
9. Configuration
Events that require CSA validation include CSI, CSM, and CSL steps in their ProcessDefinition. Events that do not require CSA validation simply omit these steps — the ProcessDefinition is configurable per event.
The CSA requirement flags on Event and EventCategory entities control the business rules (see Business Rules Hierarchy).
10. Source Code
10.1. Legacy WordPress Implementation (Reference)
| File | Purpose |
|---|---|
|
API client — HTTP calls to CSA endpoints via cURL |
|
Business logic — requirement hierarchy, status mapping, caching, validation rules |
|
DTO for |
|
DTO for |
|
UI for individual rider CSA status and day license purchase |
|
Review page showing all entrants' CSA status |
10.2. New Java Implementation
| File | Purpose |
|---|---|
|
Spring service — API client (currently mock, real implementation pending) |
|
Enum: ACTIVE, EXPIRED, OFFLINE, NOT_FOUND, SUSPENDED |
|
DTO for membership check response |
|
DTO for license check response |
|
RuntimeException for API failures |
|
CSI step — captures CSA ID and UCI ID |
|
CSM step — validates membership, presents action options |
|
CSL step — validates license, presents action options |
|
JSON answer for CSI step |
|
JSON answer for CSM/CSL steps |
|
Enum: VALID, EXEMPT, DAY_LICENSE, DEFERRED, PENDING |
|
Entity with |
|
Age calculator per CSA regulation (age on 31 December of current year) |