This guide explains how SAP (ABAP) communicates securely with the Google Gemini API (Generative Language API) – from certificates in STRUST to the RFC destination in SM59 to the productive HTTP POST in ABAP.
Why Gemini from SAP?
- Text analysis, summaries, classification, and prompt-based automation directly from ABAP.
- Easy connection via an API key and a standardized REST interface.
- Scalable from test operation to productive use.
Voraussetzungen
- SAP system with active HTTPS (ICM) and CommonCryptoLib.
- Permissions for STRUST and SM59.
- Valid Gemini API key.
- ABAP JSON utility, e.g.
/UI2/CL_JSON
.
Step 1: SM59 – Create HTTP destination for Google APIs
In SM59, you create an HTTP destination that SAP uses to access external web services or APIs.
For , generativelanguage.googleapis.com
the basic configuration looks like this:
Table of Contents
Create a new HTTP connection
- Click on Create .
- Connection Type G – Select HTTP connection to external server .
- Give a descriptive name (e.g. B.
GOOGLE_GENAI_API
orGOOGLE_GEMINI_API
).
Technical settings
- Host:
generativelanguage.googleapis.com
- Service No.:
443
(HTTPS) - Path prefix: leave blank for now – the full path is set in the ABAP code
(e.g. B./v1/models/gemini-2.5-flash:generateContent
).
Logon & Security
- SNC: not required (HTTPS is used).
- SSL: Select Active (SSL Client Standard)
(or your dedicated SSL client identity, if stored in STRUST).
Registration & Security
Google APIs typically don’t maintain user/password data in the destination. Authentication
is done in the ABAP code – either via API key in the header or via OAuth 2.0 tokens (header
Authorization: Bearer
).
- User/password: leave blank.
- SSL Client Certificate: Corresponds to the selection under Technical Settings.
If there are several identities, select specifically if necessary.
Special options
- HTTP proxy: only maintain if Internet access is running over it.
- Timeouts: adjust as needed.
Summary
- Host:
generativelanguage.googleapis.com
- Service:
443
- Path prefix: blank (path is set in code)
- SSL: Active, SSL Client Standard
- Login: empty (Auth in ABAP code)
Step 2: STRUST – Certificates for HTTPS access to Google APIs
In STRUST, you maintain the SSL/TLS trust anchors so that your SAP system accepts the server certificates presented by Google.
Where to register?
In STRUST, switch to the SSL client node (default) (or your specific SSL client identity). Right in the
certificate list , you can see all the certificates that are already familiar. Here, the root and – if available –
the intermediate certificates of Google’s issuing CA.
What certificates are required?
Google usually uses chains of well-known CAs such as Google Trust Services (GTS), GlobalSign or DigiCert.
In many systems, the important root certificates are already available (operating system/CCL often provide them).
If something is missing, add the chain manually.
Discovering and Exporting Certificates
https://generativelanguage.googleapis.com
in the browser.- Click on the lock icon and view the certificate chain.
- Root and, if applicable, intermediate certificates (e.g. GTS Root R1, GTS CA 1O1) in Base64-X.509 format
.cer
( or.pem
). Alternatively, download from the official pages of Google Trust Services.
Import into STRUST
- Open STRUST and select SSL client (default ).
- Use Import Certificate button and select the saved files.
- Add the certificates to the list and then back up the database.
- After importing, the entries must be visible.
Check the connection
In SM59, run a Test Connection at your destination. If the test fails, a
Root or Intermediate Certificate. Note: This test checks the network and TLS handshake, not the later
Authentication via API key or OAuth.
Summary
- Location: SSL client (default) (or the SSL client PSE used).
- Content: Trusted root/intermediate certificates of the Google CAs (e.g. GTS Root R1, GTS CA 1O1).
- Action: Check presence, import and save missing certificates.
Next step in ABAP
The actual API communication (HTTP method, header, body, authentication) then takes place in the code,
e.g. with CL_HTTP_CLIENT
or CL_REST_HTTP_CLIENT
.
Step 3: ABAP – POST request to Gemini (minimal example)
Example endpoint (v1, model gemini-2.5-flash
): /v1/models/gemini-2.5-flash:generateContent
REPORT z_ki_google_test.
DATA: lo_http TYPE REF TO if_http_client,
lv_body TYPE string,
lv_result TYPE string,
lv_api_key TYPE string VALUE '***PLACE_YOUR_API_KEY_SECURELY***'.
" 1) HTTP-Client aus SM59-Destination (G: HTTP)
cl_http_client=>create_by_destination(
EXPORTING destination = 'GEMINI'
IMPORTING client = lo_http ).
" 2) Header setzen
lo_http->request->set_method( if_http_request=>co_request_method_post ).
lo_http->request->set_header_field( name = 'Content-Type' value = 'application/json' ).
lo_http->request->set_header_field( name = 'x-goog-api-key' value = lv_api_key ).
" 3) Request-Pfad (Endpoint) – setze ihn NUR hier ODER als Präfix in SM59 (nicht beides!)
lo_http->request->set_header_field(
name = '~request_uri'
value = '/v1/models/gemini-2.5-flash:generateContent' ).
" ---------- Request-JSON aufbauen (mit name_mappings für lowercase keys) ----------
TYPES: BEGIN OF ty_part_req,
text TYPE string,
END OF ty_part_req.
TYPES: ty_t_part_req TYPE STANDARD TABLE OF ty_part_req WITH EMPTY KEY.
TYPES: BEGIN OF ty_content_req,
parts TYPE ty_t_part_req,
END OF ty_content_req.
TYPES: ty_t_content_req TYPE STANDARD TABLE OF ty_content_req WITH EMPTY KEY.
TYPES: BEGIN OF ty_request,
contents TYPE ty_t_content_req,
END OF ty_request.
DATA ls_req TYPE ty_request.
DATA(lv_prompt_text) = |Erkläre KI in einem Satz.|.
APPEND VALUE ty_content_req(
parts = VALUE #( ( text = lv_prompt_text ) ) )
TO ls_req-contents.
DATA lt_req_map TYPE /ui2/cl_json=>name_mappings.
DATA lt_res_map TYPE /ui2/cl_json=>name_mappings.
" Request-Mapping (ABAP-Feldname -> JSON-Key, lowercase)
INSERT VALUE /ui2/cl_json=>name_mapping( abap = 'CONTENTS' json = 'contents' ) INTO TABLE lt_req_map.
INSERT VALUE /ui2/cl_json=>name_mapping( abap = 'PARTS' json = 'parts' ) INTO TABLE lt_req_map.
INSERT VALUE /ui2/cl_json=>name_mapping( abap = 'TEXT' json = 'text' ) INTO TABLE lt_req_map.
" Response-Mapping
INSERT VALUE /ui2/cl_json=>name_mapping( abap = 'CANDIDATES' json = 'candidates' ) INTO TABLE lt_res_map.
INSERT VALUE /ui2/cl_json=>name_mapping( abap = 'CONTENT' json = 'content' ) INTO TABLE lt_res_map.
INSERT VALUE /ui2/cl_json=>name_mapping( abap = 'PARTS' json = 'parts' ) INTO TABLE lt_res_map.
INSERT VALUE /ui2/cl_json=>name_mapping( abap = 'TEXT' json = 'text' ) INTO TABLE lt_res_map.
lv_body = /ui2/cl_json=>serialize(
data = ls_req
name_mappings = lt_req_map
compress = abap_true ).
" 4) Senden & Empfangen
lo_http->request->set_cdata( lv_body ).
lo_http->send( ).
lo_http->receive( ).
" 5) Antwort lesen (Status + Body)
DATA: lv_status TYPE i,
lv_reason TYPE string.
lo_http->response->get_status(
IMPORTING
code = lv_status
reason = lv_reason ).
lv_result = lo_http->response->get_cdata( ).
" ---------- Response-JSON deserialisieren ----------
TYPES: BEGIN OF ty_part_res,
text TYPE string,
END OF ty_part_res.
TYPES: ty_t_part_res TYPE STANDARD TABLE OF ty_part_res WITH EMPTY KEY.
TYPES: BEGIN OF ty_content_res,
parts TYPE ty_t_part_res,
END OF ty_content_res.
TYPES: BEGIN OF ty_candidate,
content TYPE ty_content_res,
END OF ty_candidate.
TYPES: ty_t_candidate TYPE STANDARD TABLE OF ty_candidate WITH EMPTY KEY.
TYPES: BEGIN OF ty_response,
candidates TYPE ty_t_candidate,
END OF ty_response.
DATA ls_resp TYPE ty_response.
TRY.
/ui2/cl_json=>deserialize(
EXPORTING json = lv_result
name_mappings = lt_res_map
CHANGING data = ls_resp ).
DATA(lv_text) = ||.
IF lines( ls_resp-candidates ) > 0
AND lines( ls_resp-candidates[ 1 ]-content-parts ) > 0.
lv_text = ls_resp-candidates[ 1 ]-content-parts[ 1 ]-text.
ENDIF.
"=== ALV-Vorbereitung ======================================================
TYPES: BEGIN OF ty_company_info,
field TYPE string,
value TYPE string,
END OF ty_company_info.
DATA: lt_company_info TYPE STANDARD TABLE OF ty_company_info,
ls_company_info TYPE ty_company_info.
IF lv_status = 200 AND lv_text IS NOT INITIAL.
" Antwort in Zeilen aufteilen und Feld: Wert extrahieren
DATA(lt_lines) = VALUE stringtab( ).
SPLIT lv_text AT cl_abap_char_utilities=>newline INTO TABLE lt_lines.
LOOP AT lt_lines INTO DATA(lv_line).
IF lv_line CS ':'.
SPLIT lv_line AT ':' INTO ls_company_info-field ls_company_info-value.
CONDENSE ls_company_info-field.
SHIFT ls_company_info-value LEFT DELETING LEADING space.
ELSE.
ls_company_info-field = 'Text'.
ls_company_info-value = lv_line.
ENDIF.
APPEND ls_company_info TO lt_company_info.
CLEAR ls_company_info.
ENDLOOP.
ELSE.
" Fehlerfall als ALV zeigen
ls_company_info-field = |HTTP|.
ls_company_info-value = |{ lv_status } { lv_reason }|.
APPEND ls_company_info TO lt_company_info.
CLEAR ls_company_info.
ls_company_info-field = |Fehlermeldung|.
ls_company_info-value = lv_result.
APPEND ls_company_info TO lt_company_info.
ENDIF.
"=== ALV-Ausgabe ===========================================================
DATA: lo_alv TYPE REF TO cl_salv_table,
lo_columns TYPE REF TO cl_salv_columns_table,
lo_column TYPE REF TO cl_salv_column_table.
cl_salv_table=>factory(
IMPORTING r_salv_table = lo_alv
CHANGING t_table = lt_company_info ).
lo_columns = lo_alv->get_columns( ).
lo_columns->set_optimize( abap_true ).
lo_column ?= lo_columns->get_column( 'FIELD' ).
lo_column->set_short_text( 'Kategorie' ).
lo_column->set_medium_text( 'Kategorie' ).
lo_column->set_long_text( 'Informationskategorie' ).
lo_column ?= lo_columns->get_column( 'VALUE' ).
lo_column->set_short_text( 'Inhalt' ).
lo_column->set_medium_text( 'Beschreibung' ).
lo_column->set_long_text( 'Detail' ).
lo_alv->display( ).
CATCH cx_root INTO DATA(lx).
" Fallback: Fehler in einfacher Liste ausgeben
WRITE: / 'JSON-/ALV-Fehler:', lx->get_text( ).
WRITE: / 'Raw-Response:', / lv_result.
ENDTRY.
lo_http->close( ).
Best Practices
- Do not hardcode API key: store securely (secure store, variables, SICF handlers).
- Include timeouts and retries; Log error states clearly.
- Keep prompts short and clear.
- Model choice:
gemini-2.5-flash
for cost/speed,gemini-2.5-pro
for quality.
Troubleshooting
- 401 Unauthorized:
x-goog-api-key
missing or incorrect. - 400 Bad Request: Check JSON structure (
contents → parts → text
). - 404 Not Found: Check the model name or URI.
- SSL handshake error: CA chain incomplete in STRUST.
- Proxy/Firewall: Check SM59 proxy and network shares.
Checklist
- CA chain imported into STRUST
- SM59: Host, port 443, SSL active
- API key securely stored, header set
- Endpoint correct:
/v1/models/gemini-2.5-flash:generateContent
- Timeouts, retries and logging available