Case Study 1: REST API for Account Balance Inquiry
Background
Consolidated Savings & Loan has operated its core banking system on an IBM z/OS mainframe for over 30 years. The heart of the system is a CICS COBOL program called ACCTINQ that retrieves account information from a VSAM KSDS file. This program processes over 50,000 inquiries per day through 3270 terminal screens used by branch tellers.
The bank is launching a mobile banking application and a customer-facing web portal. Both need real-time access to account balance data. Rather than rewriting the proven COBOL inquiry logic, the architecture team decides to expose ACCTINQ as a REST API using IBM z/OS Connect EE. The existing COBOL program will continue serving teller terminals while simultaneously handling REST API requests -- the same code, the same data, with a modern API wrapper.
This case study walks through the complete process: designing the REST API, preparing the COBOL program, creating the z/OS Connect service mapping, generating the JSON response, and handling error conditions.
Problem Statement
Create a RESTful account balance inquiry service that:
- Accepts an HTTP GET request with an account number as a path parameter.
- Maps the request to the existing COBOL CICS program's COMMAREA.
- Executes the COBOL program to retrieve account data from VSAM.
- Maps the COBOL response back to a JSON response.
- Returns appropriate HTTP status codes for success, not-found, and error conditions.
- Generates clean, well-formatted JSON that mobile and web clients can consume directly.
The REST API Design
Before touching any COBOL code, the team designs the API contract:
GET /api/v1/accounts/{accountNumber}/balance
Path Parameter:
accountNumber: 10-digit account number (string)
Success Response (200 OK):
{
"accountNumber": "1000234567",
"accountType": "SAVINGS",
"ownerName": "MARTINEZ, ELENA J",
"currentBalance": "25430.75",
"availableBalance": "24930.75",
"holdAmount": "500.00",
"currency": "USD",
"lastTransactionDate": "2025-04-01",
"accountStatus": "ACTIVE"
}
Not Found Response (404):
{
"error": {
"code": "ACCT_NOT_FOUND",
"message": "Account 9999999999 not found"
}
}
Error Response (500):
{
"error": {
"code": "SYSTEM_ERROR",
"message": "Internal processing error. Contact support."
}
}
The COBOL CICS Program
The COBOL program receives the account number through its COMMAREA, looks up the account in the VSAM file, and returns the results in the same COMMAREA. The program does not know or care whether it was invoked from a 3270 terminal, a web service, or a REST API -- it processes the COMMAREA identically in all cases.
The COMMAREA Copybook
*================================================================
* COPYBOOK: ACCTINQC
* Account Inquiry COMMAREA layout
* Used by: ACCTINQ program, z/OS Connect mapping
*================================================================
01 ACCTINQ-COMMAREA.
* --- Request fields ---
05 AIC-REQUEST.
10 AIC-REQ-ACCT-NUM PIC X(10).
10 AIC-REQ-ACTION PIC X(3).
88 AIC-ACTION-BALANCE VALUE "BAL".
88 AIC-ACTION-DETAIL VALUE "DTL".
88 AIC-ACTION-HISTORY VALUE "HST".
* --- Response fields ---
05 AIC-RESPONSE.
10 AIC-RESP-CODE PIC 9(2).
88 AIC-RESP-SUCCESS VALUE 00.
88 AIC-RESP-NOT-FOUND VALUE 04.
88 AIC-RESP-FILE-ERR VALUE 08.
88 AIC-RESP-INVALID VALUE 12.
10 AIC-RESP-MESSAGE PIC X(50).
05 AIC-ACCOUNT-DATA.
10 AIC-ACCT-NUMBER PIC X(10).
10 AIC-ACCT-TYPE PIC X(10).
10 AIC-OWNER-NAME PIC X(30).
10 AIC-CURRENT-BAL PIC S9(11)V99.
10 AIC-AVAIL-BAL PIC S9(11)V99.
10 AIC-HOLD-AMOUNT PIC 9(9)V99.
10 AIC-CURRENCY PIC X(3).
10 AIC-LAST-TRANS-DATE PIC X(10).
10 AIC-ACCT-STATUS PIC X(10).
The Account Inquiry Program
IDENTIFICATION DIVISION.
PROGRAM-ID. ACCTINQ.
*================================================================
* PROGRAM: ACCTINQ
* PURPOSE: Account balance inquiry via VSAM KSDS lookup
* INVOKED: CICS LINK (from terminals, web services, REST API)
* COMMAREA: ACCTINQC copybook
*================================================================
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-PROGRAM-NAME PIC X(8) VALUE "ACCTINQ".
01 WS-VSAM-RECORD.
05 WS-VS-ACCT-NUM PIC X(10).
05 WS-VS-ACCT-TYPE PIC X(10).
05 WS-VS-OWNER PIC X(30).
05 WS-VS-BALANCE PIC S9(11)V99 COMP-3.
05 WS-VS-AVAIL PIC S9(11)V99 COMP-3.
05 WS-VS-HOLD PIC 9(9)V99 COMP-3.
05 WS-VS-CURRENCY PIC X(3).
05 WS-VS-LAST-TXN PIC X(10).
05 WS-VS-STATUS PIC X(10).
05 WS-VS-FILLER PIC X(50).
01 WS-RESP PIC S9(8) COMP VALUE 0.
01 WS-RESP2 PIC S9(8) COMP VALUE 0.
LINKAGE SECTION.
COPY ACCTINQC.
PROCEDURE DIVISION.
0000-MAIN.
EXEC CICS ADDRESS
COMMAREA(ADDRESS OF ACCTINQ-COMMAREA)
END-EXEC
PERFORM 1000-VALIDATE-INPUT
IF AIC-RESP-SUCCESS
PERFORM 2000-READ-ACCOUNT
END-IF
IF AIC-RESP-SUCCESS
PERFORM 3000-BUILD-RESPONSE
END-IF
EXEC CICS RETURN END-EXEC
.
1000-VALIDATE-INPUT.
IF AIC-REQ-ACCT-NUM = SPACES OR LOW-VALUES
SET AIC-RESP-INVALID TO TRUE
MOVE "Account number is required"
TO AIC-RESP-MESSAGE
ELSE IF FUNCTION LENGTH(
FUNCTION TRIM(AIC-REQ-ACCT-NUM))
< 10
SET AIC-RESP-INVALID TO TRUE
MOVE "Account number must be 10 digits"
TO AIC-RESP-MESSAGE
ELSE
SET AIC-RESP-SUCCESS TO TRUE
MOVE SPACES TO AIC-RESP-MESSAGE
END-IF
.
2000-READ-ACCOUNT.
MOVE AIC-REQ-ACCT-NUM TO WS-VS-ACCT-NUM
EXEC CICS READ
FILE("ACCTFILE")
INTO(WS-VSAM-RECORD)
RIDFLD(WS-VS-ACCT-NUM)
RESP(WS-RESP)
RESP2(WS-RESP2)
END-EXEC
EVALUATE WS-RESP
WHEN DFHRESP(NORMAL)
SET AIC-RESP-SUCCESS TO TRUE
WHEN DFHRESP(NOTFND)
SET AIC-RESP-NOT-FOUND TO TRUE
STRING "Account "
AIC-REQ-ACCT-NUM
" not found"
DELIMITED SIZE
INTO AIC-RESP-MESSAGE
WHEN OTHER
SET AIC-RESP-FILE-ERR TO TRUE
STRING "VSAM read error. RESP="
WS-RESP " RESP2=" WS-RESP2
DELIMITED SIZE
INTO AIC-RESP-MESSAGE
END-EVALUATE
.
3000-BUILD-RESPONSE.
MOVE WS-VS-ACCT-NUM TO AIC-ACCT-NUMBER
MOVE WS-VS-ACCT-TYPE TO AIC-ACCT-TYPE
MOVE WS-VS-OWNER TO AIC-OWNER-NAME
MOVE WS-VS-BALANCE TO AIC-CURRENT-BAL
MOVE WS-VS-AVAIL TO AIC-AVAIL-BAL
MOVE WS-VS-HOLD TO AIC-HOLD-AMOUNT
MOVE WS-VS-CURRENCY TO AIC-CURRENCY
MOVE WS-VS-LAST-TXN TO AIC-LAST-TRANS-DATE
MOVE WS-VS-STATUS TO AIC-ACCT-STATUS
MOVE "Account inquiry successful"
TO AIC-RESP-MESSAGE
.
z/OS Connect Configuration
The z/OS Connect service maps between the REST API and the COBOL COMMAREA. While the actual configuration is done through the z/OS Connect API Toolkit, the following illustrates the conceptual mapping.
Request Mapping
The z/OS Connect service maps the URL path parameter to the COMMAREA:
HTTP GET /api/v1/accounts/{accountNumber}/balance
Maps to COMMAREA:
AIC-REQ-ACCT-NUM <-- {accountNumber} from URL path
AIC-REQ-ACTION <-- "BAL" (hardcoded by service definition)
Response Mapping
The z/OS Connect service maps the COMMAREA response to JSON, with conditional logic based on the response code:
When AIC-RESP-CODE = 00 (success):
HTTP 200 OK
JSON body from AIC-ACCOUNT-DATA fields
When AIC-RESP-CODE = 04 (not found):
HTTP 404 Not Found
JSON error body with AIC-RESP-MESSAGE
When AIC-RESP-CODE = 08 or 12 (error):
HTTP 500 Internal Server Error
JSON error body with generic message (suppress internal details)
The Service Definition (Conceptual)
{
"serviceName": "accountBalanceInquiry",
"serviceType": "cicsCommarea",
"cicsConnection": {
"host": "mainframe.bank.com",
"port": 1490,
"programName": "ACCTINQ"
},
"requestMapping": {
"commarea": "ACCTINQC",
"fields": [
{
"jsonPath": "$.accountNumber",
"cobolField": "AIC-REQ-ACCT-NUM",
"source": "pathParam"
},
{
"cobolField": "AIC-REQ-ACTION",
"value": "BAL",
"source": "constant"
}
]
},
"responseMapping": {
"successCondition": "AIC-RESP-CODE == '00'",
"httpStatusMapping": {
"00": 200,
"04": 404,
"08": 500,
"12": 400
}
}
}
JSON Response Generation
For environments where z/OS Connect mapping is not available or where the COBOL program needs direct control over JSON output, the program can generate JSON itself. Here is an enhanced version of the response builder that produces JSON:
IDENTIFICATION DIVISION.
PROGRAM-ID. ACCTJSON.
*================================================================
* Enhanced account inquiry with direct JSON response generation
* Used when z/OS Connect mapping is not available
*================================================================
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-JSON-RESPONSE PIC X(2000) VALUE SPACES.
01 WS-JSON-LENGTH PIC 9(5) VALUE 0.
01 WS-SUCCESS-RESPONSE.
05 ACCOUNTNUMBER PIC X(10)
JSON NAME "accountNumber".
05 ACCOUNTTYPE PIC X(10)
JSON NAME "accountType".
05 OWNERNAME PIC X(30)
JSON NAME "ownerName".
05 CURRENTBALANCE PIC X(14)
JSON NAME "currentBalance".
05 AVAILABLEBALANCE PIC X(14)
JSON NAME "availableBalance".
05 HOLDAMOUNT PIC X(12)
JSON NAME "holdAmount".
05 CURRENCY-CD PIC X(3)
JSON NAME "currency".
05 LASTTRANSACTIONDATE PIC X(10)
JSON NAME "lastTransactionDate".
05 ACCOUNTSTATUS PIC X(10)
JSON NAME "accountStatus".
01 WS-ERROR-RESPONSE.
05 ERROR-INFO
JSON NAME "error".
10 ERROR-CODE PIC X(20)
JSON NAME "code".
10 ERROR-MSG PIC X(80)
JSON NAME "message".
01 WS-FMT-BAL PIC -(11)9.99.
01 WS-FMT-AVAIL PIC -(11)9.99.
01 WS-FMT-HOLD PIC -(9)9.99.
LINKAGE SECTION.
COPY ACCTINQC.
PROCEDURE DIVISION.
4000-GENERATE-JSON-RESPONSE.
EVALUATE TRUE
WHEN AIC-RESP-SUCCESS
PERFORM 4100-BUILD-SUCCESS-JSON
WHEN AIC-RESP-NOT-FOUND
PERFORM 4200-BUILD-NOTFOUND-JSON
WHEN OTHER
PERFORM 4300-BUILD-ERROR-JSON
END-EVALUATE
.
4100-BUILD-SUCCESS-JSON.
MOVE AIC-ACCT-NUMBER TO ACCOUNTNUMBER
MOVE FUNCTION TRIM(AIC-ACCT-TYPE)
TO ACCOUNTTYPE
MOVE FUNCTION TRIM(AIC-OWNER-NAME)
TO OWNERNAME
MOVE AIC-CURRENT-BAL TO WS-FMT-BAL
MOVE FUNCTION TRIM(WS-FMT-BAL)
TO CURRENTBALANCE
MOVE AIC-AVAIL-BAL TO WS-FMT-AVAIL
MOVE FUNCTION TRIM(WS-FMT-AVAIL)
TO AVAILABLEBALANCE
MOVE AIC-HOLD-AMOUNT TO WS-FMT-HOLD
MOVE FUNCTION TRIM(WS-FMT-HOLD)
TO HOLDAMOUNT
MOVE AIC-CURRENCY TO CURRENCY-CD
MOVE AIC-LAST-TRANS-DATE
TO LASTTRANSACTIONDATE
MOVE FUNCTION TRIM(AIC-ACCT-STATUS)
TO ACCOUNTSTATUS
JSON GENERATE WS-JSON-RESPONSE
FROM WS-SUCCESS-RESPONSE
COUNT WS-JSON-LENGTH
ON EXCEPTION
PERFORM 4300-BUILD-ERROR-JSON
END-JSON
.
4200-BUILD-NOTFOUND-JSON.
MOVE "ACCT_NOT_FOUND" TO ERROR-CODE
MOVE FUNCTION TRIM(AIC-RESP-MESSAGE)
TO ERROR-MSG
JSON GENERATE WS-JSON-RESPONSE
FROM WS-ERROR-RESPONSE
COUNT WS-JSON-LENGTH
ON EXCEPTION
MOVE '{"error":{"code":"SYSTEM_ERROR"'
& ',"message":"JSON generation failed"}}'
TO WS-JSON-RESPONSE
MOVE FUNCTION LENGTH(
FUNCTION TRIM(WS-JSON-RESPONSE))
TO WS-JSON-LENGTH
END-JSON
.
4300-BUILD-ERROR-JSON.
MOVE "SYSTEM_ERROR" TO ERROR-CODE
MOVE "Internal processing error. Contact support."
TO ERROR-MSG
JSON GENERATE WS-JSON-RESPONSE
FROM WS-ERROR-RESPONSE
COUNT WS-JSON-LENGTH
ON EXCEPTION
MOVE '{"error":{"code":"SYSTEM_ERROR"'
& ',"message":"JSON generation failed"}}'
TO WS-JSON-RESPONSE
MOVE FUNCTION LENGTH(
FUNCTION TRIM(WS-JSON-RESPONSE))
TO WS-JSON-LENGTH
END-JSON
.
Solution Walkthrough
Request Flow
When a mobile app makes a request like GET /api/v1/accounts/1000234567/balance, the following sequence occurs:
- z/OS Connect receives the HTTP request and extracts the account number from the URL path.
- z/OS Connect maps the request to a COMMAREA, setting
AIC-REQ-ACCT-NUMto "1000234567" andAIC-REQ-ACTIONto "BAL". - z/OS Connect issues an EXEC CICS LINK to the ACCTINQ program, passing the populated COMMAREA.
- ACCTINQ validates the input, reads the VSAM file, and populates the response fields in the COMMAREA.
- z/OS Connect receives the COMMAREA response and maps it to JSON.
- z/OS Connect returns the HTTP response with the appropriate status code and JSON body.
The COBOL program is unaware that it was invoked by a REST API. It processed a COMMAREA -- the same COMMAREA it has processed for 30 years. The only change was adding the JSON NAME clauses to the copybook for direct JSON generation scenarios.
Error Handling Strategy
The error handling follows a defense-in-depth approach:
- Input validation catches malformed account numbers before the VSAM read.
- VSAM RESP codes are translated to business-meaningful response codes (not-found vs. file error).
- z/OS Connect response mapping translates COBOL response codes to HTTP status codes.
- JSON generation failure falls back to a hardcoded error JSON string -- the last resort.
- Internal error details (VSAM RESP codes, file names) are logged but not exposed in the API response to prevent information leakage.
Performance Considerations
The API responds within the bank's 200ms SLA because: - The COBOL program performs a single VSAM keyed read (typically under 5ms). - CICS routing and COMMAREA passing add minimal overhead (1-2ms). - z/OS Connect JSON mapping is compiled and cached (10-20ms first time, under 5ms cached). - Network latency between z/OS Connect and CICS is negligible (same LPAR).
The entire round trip from HTTP request to HTTP response typically completes in 30-50ms, well within the SLA.
Discussion Questions
-
Backward compatibility: The ACCTINQ program now serves both 3270 terminals and REST APIs. What risks does this dual-use introduce? How would you ensure that changes for the API do not break the terminal flow?
-
Security: The REST API is exposed to mobile apps over the public internet. What security measures should be implemented at each layer (API gateway, z/OS Connect, CICS, VSAM)?
-
Versioning: If the JSON response format needs to change (e.g., adding a field, renaming a field), how would you handle API versioning? What impact does this have on the COBOL program and the z/OS Connect mapping?
-
Scalability: The current system handles 50,000 inquiries per day from terminals. The mobile app is expected to generate 500,000 requests per day. What changes are needed at the CICS, z/OS Connect, and infrastructure levels to support this 10x increase?
-
Alternative approaches: Instead of z/OS Connect, the team considered (a) writing a Java wrapper that calls CICS via CTG, or (b) using CICS Web Services with SOAP. Compare these alternatives to the z/OS Connect approach in terms of development effort, maintenance burden, and performance.