Skip to content

HTTP Workshop — REST APIs with ABAP Cloud ​

Position in the course: After Lesson 20 — RAP & CDS Extensibility and before Lesson 21 — Consuming SAP S/4HANA APIs with OData and Clean Core. This workshop stands on its own: you will not need a RAP business object, EDMX file, Service Consumption Model or S/4HANA API.

Workshop format: Three hours of guided learning and three hours of independent practice. Work through the exercises in order, using the checkpoints to confirm each result.

Development environment: SAP BTP ABAP Trial with Eclipse ABAP Development Tools (ADT). The public-API calls are subject to outbound connectivity in your tenant. A working connection to the preconfigured S/4HANA service does not automatically prove access to other public websites.

1. Why does an ABAP developer need HTTP? ​

Imagine you are developing an application that needs information stored by another application. For example, a support system may need a list of public test posts. You don't own the other application's database. Instead, you send a request to an API, and that API returns a response.

In this lesson you will use a free test service called JSONPlaceholder. It behaves like a small REST API. You can retrieve a post, request the creation of a post, update a post and delete a post. The last three operations are simulated: JSONPlaceholder returns realistic responses but does not save the changes.

The core idea: An API is a published way for software to exchange information without directly accessing each other's internal storage.

The exchange ​

text
Your ABAP class (HTTP client)
    |
    |  HTTP request: method + URL + optional JSON body
    v
JSONPlaceholder REST API
    |
    |  HTTP response: status + optional JSON body
    v
Your ABAP class (checks status and reads JSON)

What you will learn ​

By the end, you can:

  • explain API, endpoint, HTTP method, request, response, status code, and JSON;
  • call a public REST API using CL_HTTP_DESTINATION_PROVIDER and CL_WEB_HTTP_CLIENT_MANAGER;
  • execute GET, POST, PUT and DELETE requests;
  • set a JSON Content-Type header and send a JSON request body;
  • distinguish an HTTP success from proof that information was actually saved;
  • read JSON text and convert a simple JSON object into an ABAP structure using XCO;
  • identify a 404 response and distinguish it from an ABAP connection error;
  • send an Authorization: Bearer ... header in a controlled test and recognize HTTP 401;
  • distinguish Bearer, Basic and API-key authentication headers without putting real secrets in source code;
  • locate an unfamiliar REST API and learn how to use its documentation safely.

2. What is HTTP? What is REST? What is JSON? ​

HTTP is the protocol used to request and return information across the web. REST is an API design style that uses resources, URLs and HTTP methods. JSON is a text representation of data; it is not a database and not an API itself.

Open these links in your browser:

The /posts/1 response has properties similar to:

json
{
  "userId": 1,
  "id": 1,
  "title": "...",
  "body": "..."
}

userId identifies the sample author; id identifies the post. The exact title is unimportant for this lesson.

Four essential HTTP methods ​

MethodWhat it asks the server to doJSONPlaceholder URLRequest body?
GETRead a posthttps://jsonplaceholder.typicode.com/posts/1No
POSTCreate a posthttps://jsonplaceholder.typicode.com/postsYes
PUTReplace/update a particular posthttps://jsonplaceholder.typicode.com/posts/1Yes, a complete post
DELETEDelete a particular posthttps://jsonplaceholder.typicode.com/posts/1No

A resource's URL and the HTTP method are separate choices. GET and DELETE can target the same URL but request very different behavior.

Common status codes ​

StatusMeaningWhat to do
200 OKRequest succeededRead response when present
201 CreatedServer accepted a create requestInspect returned ID and body; verify persistence separately
204 No ContentSuccess without a bodyDon't attempt to parse an empty response
400 Bad RequestRequest is invalidCheck the payload, parameters and URL
401 UnauthorizedAuthentication missing or invalidCheck the configured authentication
403 ForbiddenAccess is not permittedCheck API permissions
404 Not FoundNo resource at this URLCheck the path and identifier
500+Server-side failureReview details and retry appropriately

Important: In JSONPlaceholder, POST/PUT/DELETE may produce successful statuses and realistic responses, but no persistent mutation occurs. This is a special property of the teaching service, not how a normal production API behaves.

3. Prepare Eclipse ADT ​

  1. Open Eclipse and switch to the ABAP perspective.
  2. Open your SAP BTP ABAP Trial project.
  3. Select a development package where you can create ABAP classes (or create ZPKG_REST_001).
  4. For every example, replace the suffix 001 with your assigned identifier both in the ADT class name and in the source code.
  5. To create a class: right-click the package → New → ABAP Class, enter the class name, and choose Finish.
  6. To activate: Ctrl+F3. To run a class implementing IF_OO_ADT_CLASSRUN: F9.

Connectivity preflight: browser versus ABAP ​

First, open https://jsonplaceholder.typicode.com/posts/1 in your browser. This checks the browser's connection only.

The next exercise checks whether the ABAP system itself can reach the public service. If you receive a network or destination exception, do not assume your code or the site is broken: your BTP Trial tenant may restrict external outbound requests. Use the fallback in Section 12 and inform your instructor of the exact error. HTTP 404 is different: it means the API did answer your request.

4. Guided exercise — GET: read a post from ABAP ​

Create the class ZCL_REST_GET_001 and replace all generated source with:

abap
CLASS zcl_rest_get_001 DEFINITION
  PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.
ENDCLASS.

CLASS zcl_rest_get_001 IMPLEMENTATION.
  METHOD if_oo_adt_classrun~main.
    TRY.
        "Create a destination using a public test URL.
        DATA(destination) =
          cl_http_destination_provider=>create_by_url(
            i_url = 'https://jsonplaceholder.typicode.com/posts/1' ).

        DATA(client) =
          cl_web_http_client_manager=>create_by_http_destination(
            i_destination = destination ).

        "Tell the API that JSON is an acceptable format.
        DATA(request) = client->get_http_request( ).
        request->set_header_field(
          i_name  = 'Accept'
          i_value = 'application/json' ).

        "Send GET and inspect the response.
        DATA(response) = client->execute(
          i_method = if_web_http_client=>get ).
        DATA(status) = response->get_status( ).

        out->write( |HTTP status: { status-code }| ).
        IF status-code = 200.
          out->write( response->get_text( ) ).
        ELSE.
          out->write( 'The request did not return HTTP 200.' ).
        ENDIF.

        client->close( ).
      CATCH cx_root INTO DATA(error).
        out->write( |ABAP / connection error: { error->get_text( ) }| ).
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

Activate and run. If allowed by the Trial system, the output should contain HTTP status: 200 and a JSON object with id, userId, title and body.

Read the code from top to bottom ​

  1. create_by_url: identifies the remote endpoint.
  2. create_by_http_destination: creates a client able to send HTTP requests.
  3. get_http_request: obtains the request so that we can add headers and a body.
  4. execute( ...=>get ): sends an HTTP GET.
  5. get_status: reads the HTTP result code.
  6. get_text: retrieves the JSON response as plain text.
  7. close: releases the HTTP client.
  8. CATCH: handles ABAP exceptions, which are not the same as HTTP 404/500 responses.

Checkpoint: You should be able to point to the lines that specify the URL, method and HTTP status check.

5. Why is POST different from GET? ​

A GET usually does not send a request body. POST commonly sends data to create a resource. For JSONPlaceholder, the body is a JSON object such as:

json
{
  "title": "Stock-check training",
  "body": "Created from an ABAP Cloud class",
  "userId": 1
}

Notice that the URL is /posts (the collection). The server chooses an example response ID. The API may return 201 and a new-looking object, but it has not stored it.

6. Guided exercise — all four methods in one class ​

Create ZCL_REST_METHODS_001. This class uses a small reusable send_request method to show what is common to every request; the main method makes the four distinct operations easy to compare.

abap
CLASS zcl_rest_methods_001 DEFINITION
  PUBLIC FINAL CREATE PUBLIC.

  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.

  PRIVATE SECTION.
    METHODS send_request
      IMPORTING
        iv_label  TYPE string
        iv_url    TYPE string
        iv_method TYPE if_web_http_client=>method
        io_out    TYPE REF TO if_oo_adt_classrun_out
        iv_json   TYPE string OPTIONAL.
ENDCLASS.

CLASS zcl_rest_methods_001 IMPLEMENTATION.

  METHOD if_oo_adt_classrun~main.

    "GET: Read an existing resource.
    send_request(
      iv_label  = 'GET existing post'
      iv_url    = 'https://jsonplaceholder.typicode.com/posts/1'
      iv_method = if_web_http_client=>get
      io_out    = out ).

    "POST: Send JSON to the posts collection.
    send_request(
      iv_label  = 'POST new post (simulated)'
      iv_url    = 'https://jsonplaceholder.typicode.com/posts'
      iv_method = if_web_http_client=>post
      iv_json   = '{"title":"Stock-check training","body":"Created from ABAP","userId":1}'
      io_out    = out ).

    "PUT: Send the complete replacement representation.
    send_request(
      iv_label  = 'PUT existing post (simulated)'
      iv_url    = 'https://jsonplaceholder.typicode.com/posts/1'
      iv_method = if_web_http_client=>put
      iv_json   = '{"id":1,"title":"Revised stock-check","body":"Updated from ABAP","userId":1}'
      io_out    = out ).

    "DELETE: No JSON request body needed here.
    send_request(
      iv_label  = 'DELETE existing post (simulated)'
      iv_url    = 'https://jsonplaceholder.typicode.com/posts/1'
      iv_method = if_web_http_client=>delete
      io_out    = out ).

    "Read it again: changes are NOT saved by this fake API.
    send_request(
      iv_label  = 'GET again - original post still exists'
      iv_url    = 'https://jsonplaceholder.typicode.com/posts/1'
      iv_method = if_web_http_client=>get
      io_out    = out ).

  ENDMETHOD.

  METHOD send_request.
    io_out->write( |--- { iv_label } ---| ).

    TRY.
        "A fresh client for each exercise operation.
        DATA(destination) =
          cl_http_destination_provider=>create_by_url(
            i_url = iv_url ).
        DATA(client) =
          cl_web_http_client_manager=>create_by_http_destination(
            i_destination = destination ).

        DATA(request) = client->get_http_request( ).
        request->set_header_field(
          i_name = 'Accept' i_value = 'application/json' ).

        IF iv_json IS NOT INITIAL.
          request->set_content_type( 'application/json; charset=UTF-8' ).
          request->set_text( iv_json ).
          io_out->write( |Request body: { iv_json }| ).
        ENDIF.

        DATA(response) = client->execute( i_method = iv_method ).
        DATA(status) = response->get_status( ).
        DATA(body) = response->get_text( ).

        io_out->write( |HTTP status: { status-code }| ).
        IF status-code >= 200 AND status-code < 300.
          io_out->write( 'HTTP request succeeded.' ).
        ELSE.
          io_out->write( 'HTTP request returned an error status.' ).
        ENDIF.

        IF body IS INITIAL.
          io_out->write( '(No response body)' ).
        ELSE.
          io_out->write( body ).
        ENDIF.

        client->close( ).
      CATCH cx_root INTO DATA(error).
        io_out->write( |ABAP / connection error: { error->get_text( ) }| ).
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

Expected observations ​

OperationCommon example resultWhat it proves
GET /posts/1200, existing JSON postData was read
POST /posts201, JSON with apparent ID 101Create request was accepted/simulated
PUT /posts/1200, replacement JSONUpdate request was accepted/simulated
DELETE /posts/1Often 200 and {}; 2xx with empty body also possibleDelete request was accepted/simulated
GET /posts/1 again200, original postNeither PUT nor DELETE persisted

Do not rely on the exact text of the posts. Check the status, resource ID, request body and response shape. The simulated changes do not persist. If the first GET fails due to connectivity, later methods are unlikely to work until that is resolved.

7. PUT and DELETE are not the same as POST ​

  • POST targets a collection (/posts) and submits a new representation. Real APIs often use it to create a record.
  • PUT targets a particular record (/posts/1) and sends a replacement representation. It is not the same as a partial update; that is often done with PATCH.
  • DELETE targets an identified resource and requests removal. There is normally no JSON request body in our example.
  • In a real API, the allowed operations, authorizations, validation requirements and response statuses depend on its contract.

Safety rule: The public site is explicitly designed for simulated writes. Do not send POST, PUT or DELETE to the shared S/4HANA Product API used in Lesson 21; its Trial scenario is read-only and should not be used as a write-testing target.

8. One more useful skill — parse JSON into ABAP ​

When you called GET, response->get_text( ) returned JSON text. ABAP can display that text, but a real program often needs its individual fields.

In the GET class (ZCL_REST_GET_001), insert the following inside the IF status-code = 200. block, after displaying the response body:

abap
TYPES: BEGIN OF ty_post,
         user_id TYPE i,
         id      TYPE i,
         title   TYPE string,
         body    TYPE string,
       END OF ty_post.

DATA(post) = VALUE ty_post( ).

xco_cp_json=>data->from_string( response->get_text( )
  )->apply( VALUE #(
    ( xco_cp_json=>transformation->pascal_case_to_underscore )
  ) )->write_to( REF #( post ) ).

out->write( |Parsed ID: { post-id }| ).
out->write( |Parsed user: { post-user_id }| ).
out->write( |Parsed title: { post-title }| ).

The transformation maps JSON userId to ABAP component user_id. This uses XCO JSON, a released library for ABAP Cloud; it avoids legacy non-cloud JSON helpers.

Checkpoint: Confirm that the parsed ID is 1 and the title is a nonempty ABAP string. You are now processing data, not just displaying a response.

Optional extension: Use xco_cp_json=>data->from_abap( post )->apply( VALUE #( ( xco_cp_json=>transformation->underscore_to_camel_case ) ) )->to_string( ) to serialize an ABAP structure into JSON for a POST or PUT, rather than constructing JSON text manually. Check the field mapping before sending the request.

9. Error-handling exercise: what if a post doesn't exist? ​

Copy the GET class to ZCL_REST_NOTFOUND_001 (update the ABAP class name in the source) and change only the URL to:

https://jsonplaceholder.typicode.com/posts/999999

Run it and record the HTTP status. You should normally see 404. A 404 response means the remote API has answered and says the resource is missing; it is not proof that your ABAP connection failed.

Improvement challenge: Change the ELSE output to show the status code and the body (if present). Do not treat an empty body as a JSON parsing error.

10. Authentication — how a protected API recognizes the caller ​

So far, JSONPlaceholder accepted requests without a login. Many real APIs are protected. The server may require proof of who is making the request. One common place to send that proof is an HTTP header.

For example, a request may contain:

http
GET /bearer HTTP/1.1
Host: httpbin.org
Authorization: Bearer classroom-demo-token
Accept: application/json

The Authorization header is part of the HTTP request, not part of the JSON body. The server checks it before deciding whether the operation is allowed.

Three ways an API may request authentication ​

MethodExample header or ABAP callMeaning
Bearer tokenAuthorization: Bearer <token>Presents a token issued or accepted by the provider; frequently used with OAuth 2.0
Basic authenticationAuthorization: Basic <base64-credentials>Represents username/password credentials encoded for HTTP, not encrypted by Base64
API keyOften X-API-Key: <key>Presents a provider-issued key; the exact header name varies by API

HTTPS is essential when authenticating. Accept: application/json and Content-Type: application/json are not authentication headers.

Guided ABAP exercise — a demonstration Bearer header ​

Open https://httpbin.org/bearer in a browser. Without an Authorization header, the service normally returns HTTP 401. The endpoint is intended for demonstrating Bearer headers. Its demonstration token is not a real OAuth access token; the test service checks the format/presence of the header rather than authenticating you against a real identity provider.

Create the ABAP class ZCL_REST_AUTH_001 in ADT. Replace 001 consistently with your identifier:

abap
CLASS zcl_rest_auth_001 DEFINITION
  PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.
ENDCLASS.

CLASS zcl_rest_auth_001 IMPLEMENTATION.
  METHOD if_oo_adt_classrun~main.
    TRY.
        "Public test endpoint: accepts a demonstration Bearer header.
        DATA(destination) =
          cl_http_destination_provider=>create_by_url(
            i_url = 'https://httpbin.org/bearer' ).

        DATA(client) =
          cl_web_http_client_manager=>create_by_http_destination(
            i_destination = destination ).

        DATA(request) = client->get_http_request( ).
        request->set_header_field(
          i_name  = 'Authorization'
          i_value = 'Bearer classroom-demo-token' ).
        request->set_header_field(
          i_name  = 'Accept'
          i_value = 'application/json' ).

        DATA(response) = client->execute(
          i_method = if_web_http_client=>get ).
        DATA(status) = response->get_status( ).

        out->write( |HTTP status: { status-code }| ).
        IF status-code = 200.
          out->write( 'Bearer header accepted by test server.' ).
        ELSEIF status-code = 401.
          out->write( 'Authentication header missing or rejected.' ).
        ELSE.
          out->write( 'Unexpected response from test endpoint.' ).
        ENDIF.

        "The test service may echo the token: never print real tokens.
        out->write( response->get_text( ) ).
        client->close( ).

      CATCH cx_root INTO DATA(error).
        out->write( |HTTP / ABAP error: { error->get_text( ) }| ).
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

Test A: Activate with Ctrl+F3, run with F9, and look for HTTP 200 and a response such as {"authenticated":true,"token":"classroom-demo-token"}. This is an example response, not guaranteed live output.

Test B: Comment out only the request->set_header_field call for Authorization, activate and run again. The service should normally return HTTP 401. Restore the header when finished.

Checkpoint: Explain why the URL, method and JSON response are not enough for a protected API; the server must also check the authentication information.

What Basic authentication looks like in ABAP ​

ABAP provides a method for a Basic authentication request:

abap
"Illustrative only. Do not place real credentials in source code.
request->set_authorization_basic(
  i_username = 'sample_user'
  i_password = 'sample_password' ).

This example is not a test against JSONPlaceholder or httpbin. Basic authentication transmits encoded credentials and must be protected by HTTPS; a real application should obtain credentials from approved secure configuration, such as a communication arrangement, rather than embedding passwords in the class.

What an API-key header looks like ​

abap
"Illustrative header. The provider chooses the actual name.
request->set_header_field(
  i_name  = 'X-API-Key'
  i_value = 'EXAMPLE-ONLY-NOT-A-REAL-KEY' ).

Some APIs use an Authorization header for keys instead. Always read that API's documentation before selecting a header name or format. Do not transmit a genuine secret to public echo/test endpoints or include it in screenshots, console output, homework files or Git repositories.

Authorization and failures ​

  • 401 Unauthorized: The request lacks valid authentication or requires authentication.
  • 403 Forbidden: The server refuses the request; commonly the caller lacks sufficient permission (other restrictions are also possible).
  • Connection exception: The ABAP system could not complete the HTTP communication. This is different from receiving a 401/403 response.

Trial note: Outbound requests to httpbin.org may fail even if JSONPlaceholder or the shared S/4HANA connection works. If the test site's connection is blocked, inspect https://httpbin.org/#/Auth/get_bearer, explain the header in the class, and mark the result not executed from ABAP. Do not treat such a failure as proof that Bearer syntax is wrong.

11. Check your understanding ​

Answer these questions before starting the independent exercise:

  1. Why do GET and DELETE produce different results even when they use the same URL?
  2. Which requests send JSON bodies in our examples?
  3. What does the Content-Type header tell the server?
  4. How is an HTTP 404 different from an ABAP exception?
  5. Why does JSONPlaceholder return a new ID for POST but not retain it?
  6. When would ABAP developers prefer JSON parsing over displaying the raw text?
  7. Is every JSON API also an OData API? Why not?
  8. What is the difference between an Authorization header and a Content-Type header?
  9. Why should real access tokens, passwords and API keys never appear in the source of a shared class?

12. If the Trial cannot reach JSONPlaceholder ​

This section is a fallback, not evidence of successful outbound HTTP execution.

Path A — Browser plus local JSON parsing: Open https://jsonplaceholder.typicode.com/posts/1 in your browser; copy the JSON into an ABAP string and run the XCO conversion exercise from Section 8. Then read the service's POST/PUT/DELETE examples in https://jsonplaceholder.typicode.com/guide/ and explain the methods, request bodies and typical statuses. You can still complete the JSON and REST-design objectives, but the four ABAP network calls remain unverified.

Path B — Existing Trial S/4HANA connection (GET only): If your instructor confirms a working ZBTP_TRIAL_SAP_COM_0309 arrangement, run the already-proven read-only Product API GET test from Lesson 21. This verifies the ABAP HTTP request mechanism. Do not try POST/PUT/DELETE on that shared backend.

If ABAP reports a failure calling a public URL, record the exception text, whether the browser URL works, and whether the preconfigured S/4HANA connection works. Never add a fake login, access token or credential to work around an outbound restriction.

13. Completion checklist ​

14. How this workshop prepares you for Lesson 21 ​

In this workshop you used generic REST/JSON APIs, HTTP methods and authentication headers. Lesson 21 builds on your new HTTP knowledge with a real SAP S/4HANA OData API. It then introduces EDMX and the Service Consumption Model, which help ABAP work with a formally described OData service as typed ABAP data.

In the next lesson: We are not learning HTTP from scratch again; we are learning why SAP's OData client proxy can simplify the HTTP-based integration you now understand.

Sources and reference URLs ​