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:

  1. Accepts an HTTP GET request with an account number as a path parameter.
  2. Maps the request to the existing COBOL CICS program's COMMAREA.
  3. Executes the COBOL program to retrieve account data from VSAM.
  4. Maps the COBOL response back to a JSON response.
  5. Returns appropriate HTTP status codes for success, not-found, and error conditions.
  6. 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:

  1. z/OS Connect receives the HTTP request and extracts the account number from the URL path.
  2. z/OS Connect maps the request to a COMMAREA, setting AIC-REQ-ACCT-NUM to "1000234567" and AIC-REQ-ACTION to "BAL".
  3. z/OS Connect issues an EXEC CICS LINK to the ACCTINQ program, passing the populated COMMAREA.
  4. ACCTINQ validates the input, reads the VSAM file, and populates the response fields in the COMMAREA.
  5. z/OS Connect receives the COMMAREA response and maps it to JSON.
  6. 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

  1. 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?

  2. 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)?

  3. 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?

  4. 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?

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