How to use the ABAP Client Proxy to consume a public REST service.

This blog shows how the Client Proxy (CP) can be used to consume any public REST service. These features will be available from ABAP Platform 2022 and the corresponding SP’s in SAP Note: 2512479.

For our example, the PetStore-V3 REST Api from swagger has been used, it is based on the Open API 3.0 standard and has no authorization headers, so the setup is easy. We will use public http endpoints of the service to perform typical CRUD (Create, Retrieve, Update, Delete ) operations.

Although any public REST service can be consumed via CP, it is really helpful if it is based on Open API 3.0 because then the field mapping from JSON to ABAP is easy to infer.

Before we can consume the service we should analyze it to identify:

  • Entities
  • Endpoints to be consumed
  • What the request/response body looks like for each request.

In our case, we do this via postman but you can use any method of preference.

Importing the service definiton in Postman

Download the Open API 3.0 definition for the service.

Go to Workspaces->My Workspace (Or any other in your Postman) and click on APIs.

Click on Import button and open the JSON file downloaded above. If the info is as shown below, click Import.


The service name and format is fetched from the info

Identifying Entities

  • Once the definition has been loaded, expand the newly added entry “Swagger Petstore – OpenAPI 3.0″ in the navigation sub-tree on the left hand-side, then click on drafts and then the Definition Tab as highlighted below.
  • Here you can see all components, paths and other information for the service. For our purpose, we are going to consume the Pet component only.
  • Take note of the JSON structure of this object and get an understanding for all fields names, types and nesting. For example: the Tag and Category components are contained within the Pet.


Pet Component as defined in the API

Identifying Endpoints

To view and call all the API HTTP endpoints, first set the environment variable correctly.

  1. Go to draft -> Swagger Petstore – OpenAPI -> Variables and set the baseURL to
  2. Navigate to Collections in left-most Navigation pane and click on the PetStore collection. All Api endpoints should now be working.

View all endpoints and check the request/responses for each.

For this example we will use the highlighted requests only. i.e

  1. Find pet by id
  2. Update a pet using form data
  3. Delete a pet
  4. Update an existing Pet
  5. Add a new pet to petstore

Now that we have analyzed the service and decided what we want to consume, we can start our work in ABAP!

Following classes must be defined for consuming the service:

  1. The interface for structure types and constants

  2. The client proxy model

  3. The client to consume the service

Each of these is described in detail in the sections below. Just copy the code snippets in order and by the end you will have all three classes. All blocks of code have been explained thoroughly.

Note: when copying the code in this blog replace the suffix ZAF_ with your own one

Interface for structure types and constants

To facilitate our other classes and for cleanliness, we will group together constants and structure types in an interface.

Create a new ABAP interface and give it a suitable name for example “zaf_if_petstore_types“.

Structure types

The PET structure with all its underlying entities and fields has to be defined as shown. Since it contains the TAG and CATEGORY entities they have to be defined before.

ABAP definition

 TYPES: BEGIN OF tys_category, id TYPE i, name TYPE string, END OF tys_category, BEGIN OF tys_tag, id TYPE i, name TYPE string, END OF tys_tag, BEGIN OF tys_pet, id TYPE i, name TYPE string, category TYPE tys_category, photo_urls TYPE STANDARD TABLE OF string WITH DEFAULT KEY, tags TYPE STANDARD TABLE OF tys_tag WITH DEFAULT KEY, status TYPE string, END OF tys_pet.

The definition is based on the JSON structure of the entity. Looking at the XML response of a get request, we get an idea of teh structure. A more precise way is to check the Open API 3.0 schema definition via Postman

XML response of a get request (DO NOT COPY!)

<Pet> <category> <id>0</id> <name>Pug</name> </category> <id>3</id> <name>doggie</name> <photoUrls> <photoUrl></photoUrl> <photoUrl></photoUrl> <photoUrl></photoUrl> </photoUrls> <status>available</status> <tags> <tag> <id>22</id> <name>dog</name> </tag> </tags>


Next we define resource names and paths for each endpoint that we want to consume. So for all five endpoints we have to define a resource path/name.

For instance:
To call the API endpoint: GET /pet/4
the resource path /pet{id} has to be defined to call this service endpoint.
Similarly, we do this for all the five endpoints identified previously. Therefore the following constants have to be defined:

  • gcs_resourcse_paths contains the entire path with placeholders for query parameters.
  • gcs_resource_names is simply a name we use internally, you can choose whatever you want

Copy and paste this code in your interface class to define the constants

 BEGIN OF gcs_resource_names, pets TYPE /iwbep/if_v4_rest_types=>ty_internal_name VALUE `PETS`, pet_id TYPE /iwbep/if_v4_rest_types=>ty_internal_name VALUE `PET_ID`, pet_id_name_status TYPE /iwbep/if_v4_rest_types=>ty_internal_name VALUE `PET_ID_NAME_STATUS`, END OF gcs_resource_names, BEGIN OF gcs_resource_paths, pets TYPE string VALUE `/pet`, pet_id TYPE string VALUE `/pet/{id}`, pet_id_name_status TYPE string VALUE `/pet/{id}?name={name}&status={status}`, END OF gcs_resource_paths.

Defining the Client Proxy Model.

The service consumption model performs these tasks:

  • defines the structure for entities to be consumed using abap structures previous
  • defines resources, for each http endpoint.
  • define one or more operations on each resource.

Create a new class  z*_cl_rest_petstore_model and copy paste the code snippets in this section one-by-one, in order.

Class Definition

  • The public method /iwbep/if_v4_mp_basic_rest~define is an interface method which must be implemented by every consumption model class.
  • define_pet defines our resource to be consumed and takes as input the model object
  • All models must inherit the /iwbep/cl_v4_abs_model_prov base class.
    CLASS zaf_cl_rest_petstore_model DEFINITION PUBLIC INHERITING FROM /iwbep/cl_v4_abs_model_prov FINAL CREATE PUBLIC . PUBLIC SECTION. METHODS /iwbep/if_v4_mp_basic_rest~define REDEFINITION. PRIVATE SECTION. "! <p class="shorttext synchronized" lang="en">Define the structure type and resources for Pet</p> "! "! @parameter io_model | <p class="shorttext synchronized" lang="en">Proxy model</p> "! @raising /iwbep/cx_gateway | <p class="shorttext synchronized" lang="en">Gateway exception</p> METHODS define_pet IMPORTING io_model TYPE REF TO /iwbep/if_v4_rest_model RAISING /iwbep/cx_gateway . ENDCLASS.​

Class Implementation

Both methods are implemented using the code in snippets below. Just copy and paste the code snippets and everything should work. Important points are explained for each snippet

Method /iwbep/if_v4_mp_basic_rest~define .

This method just calls the inner method and passes the io_model parameter.

CLASS zaf_cl_rest_petstore_model IMPLEMENTATION. METHOD /iwbep/if_v4_mp_basic_rest~define. define_pet( CAST /iwbep/if_v4_rest_model( io_model ) ). ENDMETHOD.

Method define_pet.

The main method where we define the structure types, resources and operations that can be performed on the service.

Data Declaration

  • Each of the three entities to be consumed need an ABAP structure for structure definition.
 METHOD define_pet.
* DATA: ls_data_container_pet TYPE zaf_if_petstore_types=>tys_pet, ls_data_container_tag TYPE zaf_if_petstore_types=>tys_tag, ls_data_container_category TYPE zaf_if_petstore_types=>tys_category, lo_structured_type TYPE REF TO /iwbep/if_v4_rest_struc_type, lo_resource TYPE REF TO /iwbep/if_v4_rest_resource, lo_operation TYPE REF TO /iwbep/if_v4_rest_operation.

Defining Structures

  • The nested structure types Tag and Category must be defined first, followed by Pet
  • All primitive property names must be lowercase to match the JSON field names.
  • Since the Pet structure contains Tag and Category, they must be added as properties to the Pet Type
*Define Category lo_structured_type = io_model->create_struct_type_by_struct( iv_name = zaf_if_petstore_types=>gcs_internal_struct_type_names-category is_structure = ls_data_container_category iv_do_gen_prim_props = abap_true ). lo_structured_type->camelcase_lower_prim_prp_names( ). *Define Tag lo_structured_type = io_model->create_struct_type_by_struct( iv_name = zaf_if_petstore_types=>gcs_internal_struct_type_names-tag is_structure = ls_data_container_tag iv_do_gen_prim_props = abap_true ). lo_structured_type->camelcase_lower_prim_prp_names( ). *Define Pet lo_structured_type = io_model->create_struct_type_by_struct( iv_name = zaf_if_petstore_types=>gcs_internal_struct_type_names-pet is_structure = ls_data_container_pet iv_do_gen_prim_props = abap_true iv_do_gen_prim_prop_colls = abap_true ). lo_structured_type->camelcase_lower_prim_prp_names( ). lo_structured_type->create_structured_property( 'CATEGORY' )->set_external_name( 'category' )->set_structured_type( zaf_if_petstore_types=>gcs_internal_struct_type_names-category ). lo_structured_type->create_structured_property( 'TAGS' )->set_external_name( 'tags' )->set_structured_type( zaf_if_petstore_types=>gcs_internal_struct_type_names-tag )->set_is_collection( ).

Defining Resources and Operations

  • Resources are defined for each unique http endpoint/path.
  • the import parameters for the method create_resource are filled using the corresponding constants defined in the interface.
  • For parameters inside the path template, a structure type must also be defined using set_path_params_struct_type.
    Given this path template: /pet/{id}
    the structured type must contain one primitive property (e.g. with the name ID), that has the external name id, in our case this is simply the pet structure type.
  • Operations are defined on a resource using an HTTP verb, and can further have a request/response body
  • The response body and the request body can also be provided a structure type depending on the type of operation being performed.
    GET /pet/{id} has a response body with type PET but no request body
    POST /pet has a response and request body with type PET.
  • For certain unique operations such as delete in this case, an operation may not have a  structure type for a request body and response body both. This is because response is simple JSON “pet_deleted”, which doesn’t correspond to any type.
 lo_resource = io_model->create_resource( iv_name = zaf_if_petstore_types=>gcs_resource_names-pet_id iv_path_template = zaf_if_petstore_types=>gcs_resource_paths-pet_id ). lo_resource->set_path_params_struct_type( zaf_if_petstore_types=>gcs_internal_struct_type_names-pet )->create_operation( /iwbep/if_v4_rest_types=>gcs_http_method-get )->create_response_body( )->set_structured_type( zaf_if_petstore_types=>gcs_internal_struct_type_names-pet ). *Delete lo_resource->set_path_params_struct_type( zaf_if_petstore_types=>gcs_internal_struct_type_names-pet )->create_operation( /iwbep/if_v4_rest_types=>gcs_http_method-delete )->create_response_body( ). *Update Status and Name io_model->create_resource( iv_name = zaf_if_petstore_types=>gcs_resource_names-pet_id_name_status iv_path_template = zaf_if_petstore_types=>gcs_resource_paths-pet_id_name_status )->set_path_params_struct_type( zaf_if_petstore_types=>gcs_internal_struct_type_names-pet )->create_operation( /iwbep/if_v4_rest_types=>gcs_http_method-post )->create_response_body( )->set_structured_type( zaf_if_petstore_types=>gcs_internal_struct_type_names-pet ). lo_resource = io_model->create_resource( iv_name = zaf_if_petstore_types=>gcs_resource_names-pets iv_path_template = zaf_if_petstore_types=>gcs_resource_paths-pets ). *Create lo_operation = lo_resource->create_operation( /iwbep/if_v4_rest_types=>gcs_http_method-post ). lo_operation->create_request_body( )->set_structured_type( zaf_if_petstore_types=>gcs_internal_struct_type_names-pet ). lo_operation->create_response_body( )->set_structured_type( zaf_if_petstore_types=>gcs_internal_struct_type_names-pet ). *Update with put lo_operation = lo_resource->create_operation( /iwbep/if_v4_rest_types=>gcs_http_method-put ). lo_operation->create_request_body( )->set_structured_type( zaf_if_petstore_types=>gcs_internal_struct_type_names-pet ). lo_operation->create_response_body( )->set_structured_type( zaf_if_petstore_types=>gcs_internal_struct_type_names-pet ). END METHOD.

This concludes our model class, next we will define the client which will be used to execute the model operations.

Defining the client to consume the service

Create a new class in the same package as the model and name it z*_cl_rest_petstore_client.

Just like in the previous section, keep copying the code snippets in order of appearance. Each snippet is accompanied by a description of main concepts.

Class Definition.

The class has overall five methods, each corresponding to an operation on the API. In the Analysis section we identified five operations that we want to perform using CP, you can check them out again for reference.

For each of these operations, we will define a method, there’s also method for instantiating the client proxy object. The code snippet below defines all these methods for you. The comments also explain the purpose in detail.

CLASS zaf_cl_rest_petstore_client DEFINITION PUBLIC FINAL CREATE PUBLIC . PUBLIC SECTION. METHODS CONSTRUCTOR raising /iwbep/cx_gateway. "! <p class="shorttext synchronized" lang="en">Create a Pet</p> "! @parameter is_pet | <p class="shorttext synchronized" lang="en">typed structure containing create data</p> "! @parameter rs_pet | <p class="shorttext synchronized" lang="en">typed structure containing response data from server </p> "! @raising /iwbep/cx_gateway | <p class="shorttext synchronized" lang="en"></p> METHODS pet_create IMPORTING is_pet TYPE zaf_if_petstore_types=>tys_pet RETURNING VALUE(rs_pet) TYPE zaf_if_petstore_types=>tys_pet RAISING /iwbep/cx_gateway. "! <p class="shorttext synchronized" lang="en">Delete a Pet with supplied id</p> "! This method does not return a json response body which can be serialized in to abap structure, so the http code is returned. "! @parameter iv_id | <p class="shorttext synchronized" lang="en"></p> "! @parameter rv_http_status_code | <p class="shorttext synchronized" lang="en"></p> "! @raising /iwbep/cx_gateway | <p class="shorttext synchronized" lang="en"></p> METHODS pet_delete IMPORTING iv_id TYPE i RETURNING VALUE(rv_http_status_code) TYPE i RAISING /iwbep/cx_gateway. "! <p class="shorttext synchronized" lang="en">Get a pet with supplied id</p> "! "! @parameter iv_id | <p class="shorttext synchronized" lang="en">The id of the pet</p> "! @parameter rs_pet | <p class="shorttext synchronized" lang="en">Returned pet as ABAP structure, complete with deep fields.</p> "! @raising /iwbep/cx_gateway | <p class="shorttext synchronized" lang="en"></p> METHODS pet_read_by_id IMPORTING iv_id TYPE i RETURNING VALUE(rs_pet) TYPE zaf_if_petstore_types=>tys_pet RAISING /iwbep/cx_gateway. "! <p class="shorttext synchronized" lang="en">Update a pet using PUT</p> "! @parameter is_pet | <p class="shorttext synchronized" lang="en">typed structure containing update data</p> "! @parameter rs_pet | <p class="shorttext synchronized" lang="en">typed structure containing response data from server </p> "! @raising /iwbep/cx_gateway | <p class="shorttext synchronized" lang="en"></p> METHODS pet_update IMPORTING is_pet TYPE zaf_if_petstore_types=>tys_pet RETURNING VALUE(rs_pet) TYPE zaf_if_petstore_types=>tys_pet RAISING /iwbep/cx_gateway. "! <p class="shorttext synchronized" lang="en">Update the name and status of the pet having the supplied ID</p> "! The variables to be updated are sent as query parameters. The request type is POST "! @parameter iv_id | <p class="shorttext synchronized" lang="en">ID of the pet that is to be updated.</p> "! @parameter iv_name | <p class="shorttext synchronized" lang="en">new name for the pet</p> "! @parameter iv_status | <p class="shorttext synchronized" lang="en">new status for the pet</p> "! @parameter rs_pet | <p class="shorttext synchronized" lang="en">Updated pet </p> "! @raising /iwbep/cx_gateway | <p class="shorttext synchronized" lang="en"></p> METHODS pet_update_name_and_status IMPORTING iv_id TYPE i iv_name TYPE zaf_if_petstore_types=>tys_pet-name iv_status TYPE zaf_if_petstore_types=>tys_pet-status RETURNING VALUE(rs_pet) TYPE zaf_if_petstore_types=>tys_pet RAISING /iwbep/cx_gateway. PRIVATE SECTION. DATA: mo_client_proxy TYPE REF TO /iwbep/if_cp_client_proxy_rest, mo_http_client TYPE REF TO if_http_client. METHODS create_client_proxy RETURNING VALUE(ro_client_proxy) TYPE REF TO /iwbep/if_cp_client_proxy_rest RAISING /iwbep/cx_gateway. ENDCLASS.

Class Implementation

The implementation of each method is very similar. Using method chaining, we add the necessary information to carry out each request and also provide the payload data if required. For some methods we also receive the response data typed as PET object. The code in these sections is self-explanatory, in case of questions just write a comment.

First add the implementation class header, followed by all the method definitions.

CLASS zaf_cl_rest_petstore_client IMPLEMENTATION.



 METHOD constructor. mo_client_proxy = create_client_proxy( ). ENDMETHOD.


 METHOD pet_create.
* POST mo_client_proxy->create_resource( zaf_if_petstore_types=>gcs_resource_names-pets )->create_request( /iwbep/if_v4_rest_types=>gcs_http_method-post )->set_body_data( is_pet )->execute( )->get_body_data( IMPORTING ea_body_data = rs_pet ). ENDMETHOD.


 METHOD pet_delete.
* DELETE DATA ls_key TYPE zaf_if_petstore_types=>tys_pet. rv_http_status_code = mo_client_proxy->create_resource( zaf_if_petstore_types=>gcs_resource_names pet_id )->set_path_template_parameters( ls_key )->create_request( /iwbep/if_v4_rest_types=>gcs_http_method-delete )->execute( )->get_http_status_code( ). ENDMETHOD.


 METHOD pet_read_by_id.
*{id} GET mo_client_proxy->create_resource( zaf_if_petstore_types=>gcs_resource_names-pet_id )->set_path_template_parameters( VALUE zaf_if_petstore_types=>tys_pet( id = iv_id ) )->create_request( /iwbep/if_v4_rest_types=>gcs_http_method-get )->execute( )->get_body_data( IMPORTING ea_body_data = rs_pet ). ENDMETHOD.


 METHOD pet_update.
* PUT mo_client_proxy->create_resource( zaf_if_petstore_types=>gcs_resource_names-pets )->create_request( /iwbep/if_v4_rest_types=>gcs_http_method-put )->set_body_data( is_pet )->execute( )->get_body_data( IMPORTING ea_body_data = rs_pet ). ENDMETHOD.


 METHOD pet_update_name_and_status.
*{id}?name={name}&status={status} POST mo_client_proxy->create_resource( zaf_if_petstore_types=>gcs_resource_names-pet_id_name_status )->set_path_template_parameters( VALUE zaf_if_petstore_types=>tys_pet( id = iv_id name = iv_name status = iv_status ) )->create_request( /iwbep/if_v4_rest_types=>gcs_http_method-post )->execute( )->get_body_data( IMPORTING ea_body_data = rs_pet ). ENDMETHOD.


 METHOD create_client_proxy. DATA: ls_proxy_model_key TYPE /iwbep/if_cp_registry_types=>ty_s_proxy_model_key, lv_error_text TYPE string. cl_http_client=>create_by_url( EXPORTING url = '' IMPORTING client = mo_http_client EXCEPTIONS argument_not_found = 1 plugin_not_active = 2 internal_error = 3 pse_not_found = 4 pse_not_distrib = 5 pse_errors = 6 OTHERS = 7 ). IF sy-subrc <> 0. MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4 INTO lv_error_text. RAISE EXCEPTION TYPE lx_rest_json_pl EXPORTING iv_error_text = lv_error_text. ENDIF. "Proxy model defined in ZAF_CL_REST_PETSTORE_MODEL ls_proxy_model_key = VALUE #( repository_id = /iwbep/if_cp_registry_types=>gcs_repository_id-default proxy_model_id = 'ZAF_REST_PETSTORE' proxy_model_version = 0003 ). ro_client_proxy ?= /iwbep/cl_cp_client_proxy=>create_rest_remote_proxy( is_proxy_model_key = ls_proxy_model_key io_http_client = mo_http_client iv_do_fetch_csrf_token = abap_false iv_relative_service_root = '/api/v3'). ENDMETHOD.

Now that we have created both the Model and Client, we have to register the client proxy and setup the SSL certificate for the remote service.

Register Client Proxy

Before we can use our proxy model, it must be registered. Perform the following steps to register it:

  1. Open the Transaction /IWBEP/CP_ADMIN
  2. Click on Create and enter the details as shown below
Proxy Model ID *You can choose any value e.g ZAF_REST_PETSTORE
Version 1
Model Provider Class *Enter name of your proxy model class
Description *Enter any description
Package $TMP

After successfully completing these steps your proxy model should appear in the list of models in the transaction.

Adding SSL certificate for the remote service

The SSL certificate for the remote service has to be added in the ABAP system before it can be accessed. Refer to this blog post on how to do that.
External API Integration in SAP using REST handlers – PART 1 | SAP Blogs

Executing the Client Class Methods

Congratulations if you have come this far! Now the last step is to use our Client and Proxy Model classes to create unit tests which actually consume the service. The unit test class has one method which performs all CRUD operations and then compares actual vs expected data for each operation.

Copy this code in the unit class:

CLASS zaf_cl_petstore_test DEFINITION DEFERRED.
class zaf_cl_rest_petstore_client definition local friends
zaf_cl_petstore_test. CLASS zaf_cl_petstore_test DEFINITION
DURATION SHORT RISK LEVEL HARMLESS. PUBLIC SECTION. METHODS: pet_operations FOR TESTING RAISING /iwbep/cx_gateway. PRIVATE SECTION. DATA mo_cut TYPE REF TO zaf_cl_rest_petstore_client. METHODS: setup RAISING /iwbep/cx_gateway. ENDCLASS. CLASS zaf_cl_petstore_test IMPLEMENTATION. METHOD pet_operations. DATA: ls_payload TYPE zaf_if_petstore_types=>tys_pet, ls_response_exp TYPE zaf_if_petstore_types=>tys_pet, ls_response_act TYPE zaf_if_petstore_types=>tys_pet, ls_category TYPE zaf_if_petstore_types=>tys_category, lv_http_status_code TYPE string, lv_id TYPE i, lx_gateway TYPE REF TO /iwbep/cx_gateway. *CREATE ls_category = VALUE #( id = 20 name = 'Persian' ). ls_payload = VALUE #( id = 34 name = 'Cat' status = 'Brand New' category = ls_category photo_urls = VALUE #( ( |url_1| ) ) ). ls_response_exp = ls_payload. ls_response_act = mo_cut->pet_create( ls_payload ). "Then the expected pet data should be returned. cl_abap_unit_assert=>assert_equals( exp = ls_response_exp act = ls_response_act ). CLEAR ls_response_act. *READ BY ID "When we make a get request for a pet with this id. lv_id = 34. ls_response_act = mo_cut->pet_read_by_id( lv_id ). "Then the expected pet data should be returned. cl_abap_unit_assert=>assert_equals( exp = ls_response_exp act = ls_response_act ). CLEAR ls_response_act. *UPDATE NAME AND STATUS ls_response_act = mo_cut->pet_update_name_and_status( iv_id = 34 iv_name = 'new_name' iv_status = 'new_status' ). ls_response_exp-name = 'new_name'. ls_response_exp-status = 'new_status'. cl_abap_unit_assert=>assert_equals( exp = ls_response_exp act = ls_response_act ). *UPDATE ls_payload-name = 'Schrodingers Cat'. ls_payload-status = 'Dead and Alive'. ls_payload-category = VALUE #( id = 21 name = 'Mystery Kitty' ). ls_response_exp = ls_payload. ls_response_act = mo_cut->pet_update( ls_payload ). cl_abap_unit_assert=>assert_equals( exp = ls_response_exp act = ls_response_act ). *DELETE lv_http_status_code = mo_cut->pet_delete( 4 ). "the expected pet data should be returned. cl_abap_unit_assert=>assert_equals( exp = 200 act = lv_http_status_code ). ENDMETHOD. METHOD setup. mo_cut = NEW zaf_cl_rest_petstore_client( ). ENDMETHOD. ENDCLASS.

Once everything is set-up you can execute the unit tests and they should be green! Now pat yourself on the back and enjoy some coffee 🙂

If you enjoyed please subscribe to my Profile and share this blog with others. Also, feel free to ask any questions, provide feedback and give suggestions.

You may also follow the RESTful ABAP Programming Tag for more content and checkout these community links:

Q/A for RAP
Similar Content