APICall

Mit dem Step APICallopen in new window kann man auf HTTP-APIs und Schnittstellen zugreifen und die Antwort direkt in eine Spreadsheet einlesen. Dabei unterstützt der Step die Verarbeitung von XML und JSON Antworten.

Die Besonderheiten dieses Steps sind:

  • Antworten im XML bzw. JSON Format können direkt im Step verarbeitet werden
  • Automatische Erkennung der JSON-Struktur mit dem responseFormat Automatisches JSON-Parsing. Für volle Kontrolle über das Einlesen gibt es die manuelle Modus JSON und XML, die weiter unten beschrieben werden )
  • Unterstützung für Pagination (das sog. "Weiterblättern", falls es weitere Ergebnisse für die Abfrage gibt). Dies funktioniert nur im manuellen Modus, und nicht beim atuomatischen Parsing.

Tutorial: REST-API Anbindung

Lernen Sie in unserem Tutorial REST API Anbindung mit Synesty, wie man jede beliebige HTTP-API mit JSON oder XML anbinden kann - auch ohne Add-Ons von Synesty und ohne Programmierung in PHP, Java oder Javascript.

Unterschied zwischen APICall, SpreadsheetURLDownload und URLDownload

Dieses Cookbookopen in new window beschreibt den Unterschied der drei Steps.

HTTP Request und Response-Verarbeitung in einem

Der APICall Step vereint im Grunde genommen den URLDownloadopen in new window und den JSONReader bzw. XMLReader in einem Step. 
D.h. man kann HTTP-Requests ausführen und gleich die Antwort verarbeiten - in einem einzigen Step.

Oben drauf kann man den Step auch noch anweisen, weitere Requests (Calls) auszuführen, um sich z.B. durch ein API-Suchergebnis zu "blättern". Das wird unter Pagination beschrieben.

D.h. umgangssprachlich kann man damit Szenarien wie diese Realisieren:

  1. Rufe 1. Seite mit Produkten ab
  2. Gibt es noch weitere Ergebnisse auf der nächsten Seite?
  3. Wenn ja: Rufe 2. Seite mit Produkten ab.
  4. Gibt es noch weitere Ergebnisse auf der nächsten Seite?
  5. Wenn ja: Rufe 3. Seite mit Produkten ab.
  6. Gibt es noch weitere Ergebnisse auf der nächsten Seite?
  7. Nein. Ende der Verarbeitung.

Aufbau

Die Screenshots zeigen die elementaren Felder:

  • host - die Aufzurufende URL
  • method - die HTTP Methode (GET, POST, PUT...)
  • responseFormat - Das Format der Response. JSON oder XML.  Damit wird im Hintergrund der zu verwendende Parser definiert.
  • response parsing script - der Parsing Code analog zum Step JSONReader oder XMLReader, je nach gewähltem responseFormat. Mit diesem Script wird die Response in ein Spreadsheet umgewandelt.

Pagination

Die APICall Step kann im Vergleich zum URLDownload Step auch mehrere URLs hintereinander aufrufen, also das Weiterblättern und Aufrufen der nächsten Seite, so lange, bis das Ende der Ergebnisse erreicht ist. Dafür dient die Funktion nextUrl(), die im response parsing script verwendet werden kann, z.B.

  1. Call: http://api.myshop.com/getproductsopen in new window
  2. Call: http://api.myshop.com/getproducts?page=2open in new window
  3. Call: http://api.myshop.com/getproducts?page=3open in new window

etc.

Dies wird oft als page-based Pagination bezeichnet. Es gibt dazu noch andere Art wie z.B. cursor-based Pagination, die ähnlich funktioniert, sich aber in der Art der Parameter zum "blättern" unterscheidet.

Beispiel response parsing script

Dieses Beispiel, welches immer wieder die initiale URL (host) aufruft und einen page= Parameter mit einer hochzählenden Zahl anhängt, damit die nächste Seite aufgerufen wird.

Beispiel response parsing script (einfache paginiation mit page Parameter):


<#assign numRows = 0 />
<#assign row = target.addRow()>
<#list json as j >
 <#if (limit?? && target.size() > limit?number)><#break /></#if>
 <#assign row = target.addRow()>
 ${addColumns(row, j)}

 <#assign numRows = numRows + 1 />
</#list>

<#if ( numRows > 0 && (!limit?? || target.size() < limit?number) )>
${nextUrl( pagination(host).plusOne("page") )}
</#if>

Dieses Beispiel nutzt die Hilsfunktionen nextUrlopen in new window und paginationopen in new window für page-based Pagination.

Die pagination-Helferfunktion, versucht automatisch die nächste URL für Pagination zu erzeugen. Das Ergebnis wird wie im Beispiel an die nextUrl() Funktion übergeben. Dabei werden folgende Parameter übergeben:

  • URL, wie sie bei host eingetragen ist und einen Parameter page beinhaltet z.B. page=1. die gleichnamige Variable host beinhaltet immer die jeweilige aktuelle URL
  • .plusOne('page') sorgt dafür, dass der in host enthaltene page Parameter um 1 hochgezählt wird.
  • das Ergebnis von pagination(host).plusOne("page") ist eine neue URL, die an die nextUrl()-Funktion übergeben wird. Nach dem nächsten Aufruf enthält die Variable host dann einen um 1 hochgezählten page Parameter.
  • die Bedingung <#if ( numRows > 0 && (!limit?? || target.size() < limit?number) )> dient als Abbruchbedingung und und bedeutet sinngemäß, dass so lange weitergeblättert werden soll, solange die JSON-Antwort ein Ergebnis hat, welches zu neuen Zeilen führt. Sobald die JSON-Antwort leer ist / kein Ergebnis mehr hat, soll abgebrochen werden.

Beispiel parsingTemplte (erweiterte Pagination mit weiteren Parametern)

<#assign row = target.addRow()>
 
<#assign numRows = 0 />
 
<#list json as myJsonObject>
 <#assign row = target.addRow()>
    ${addColumns(row, myJsonObject['product'])}
    <#assign numRows = numRows + 1 />
</#list>
 
 ${nextUrl( pagination(host, json["currentPage"]?number, json["totalCount"]?number, 100 ).plusOne("page")! )}

Die pagination-Helferfunktion, versucht ebenfalls automatisch die nächste URL für Pagination zu erzeugen. Das Ergebnis wird wie im Beispiel an die nextUrl() Funktion übergeben. Dabei werden folgende Parameter übergeben:

  • aktuelle URL
  • aktuelle Seite, auf der man sich gerade befindet. Oft ist dies Teil der JSON-Antwort. (Alternativ kann man auch callcounter + 1 übergen.)
  • Anzahl Objekte im Ergebnis insgesamt (z.B. z.B. 1000 Produkte insgesamt über alle Seiten)
  • Anzahl Objekte pro Seite (z.B. 100 pro Seite. D.h für 1000 Produkte müssen Seiten 1 bis 10 abgerufen werden)
${pagination('http://api.example.com/products', 1, 1000, 100).plusOne('page')}

Das Ergebnis dieses Aufrufs wäre:

http://api.example.com/products?page=2

Die aktuelle URL kann auch viele weitere Parameter enthalten. Es wird nur der Seiten-Parameter ersetzt.

${pagination('http://api.example.com/products?filterStartDate=2022-03-23&filterEndDate=2022-03-24', 1, 1000, 100).plusOne('page')}

Das Ergebnis dieses Aufrufs wäre:

http://api.example.com/products?filterStartDate=2022-03-23&filterEndDate=2022-03-24&page=2

Die pagination() Funktion erkennt auch automatisch, wenn das Ende erreicht ist und alle Seiten alle Seiten "durchgeblättert" sind. Dann hört die nextUrl() Funktion auch auf, weitere Seiten aufzurufen und der Step ist fertig.

${pagination('http://api.example.com/products', 11, 1000, 100).plusOne('page')}

Dieser Aufruf würde einen leeren String zurückgeben, da es keine Seite 11 gibt.

Funktionen von Pagination

Es gibt mehrere Funktionen, die mit der pagination() Funktionen verwendet werden können.

  • .plusOne(pageParameterName)
  • .to(pageParameterName, pageNumber)

.plusOne(pageParameterName) - wie im Beispiel -geht zur nächsten Seite, basierend auf der aktuellen Seite. Der Name des Parameters (üblicherweise page) wird übergeben und in der aufzurufenden URL entsprechend ersetzt und hochgezählt.

Die Funktion .to(pageParameterName, pageNumber) kann verwendet werden, um direkt zu einer bestimmten Seite zu gehen, indem der page-parametername überschrieben wird.

${nextUrl( pagination(host, json["currentPage"]?number, json["totalCount"]?number, 100 ).to('page',2)}

Hier würde direkt Seite 2 aufgerufen werden. Wenn die Seite eine Zahl ist (wie im Beispiel), gibt diese Methode auch einen leeren String zurück, wenn die Seite größer ist als die Gesamtzahl der Seiten (das wäre die Abbruchbedingung).

Man kann die Pagination Funktion auch ohne Parameter für aktuelle Seite, Anzahl Objekte gesamt, oder Anzahl Objekte pro Seite aufrufen.

${pagination('http://api.example.com/products?page=1').to('page',2)}

Das Ergebnis dieses Aufrufs wäre:

http://api.example.com/products?page=2

Hier würde ebenfalls Seite 2 aufgerufen werden. In diesem Beispiel wurden keine Parameter für aktuelle Seite, Anzahl Objekte gesamt, oder Anzahl Objekte pro Seite übergeben. Daher kann die Funktion in diesem Fall auch nicht automatisch stoppen, sondern erzeugt immer wieder die gleiche URL.

Man kann als pageNumber auch eine Zeichenkette übergeben. Dann wird immer eine URL zurückgegeben, wobei der Page-Parametername ersetzt wird (das kann für Cursor-Based Pagination genutzt werden, wenn der Parameter zum Blättern eine Zeichenkette ist).

${pagination('http://api.example.com/products?after_id=abc123').to('after_id','bcd234')}

Diese würde folgende URL erzeugen.

http://api.example.com/products?after_id=bcd234

Das wird oft als Cursor-Based Pagination bezeichnet. In diesem Fällen enthält die JSON-Response einer API oft den Parameter für die nächste Seite, der dann als Parameter für den nächsten Aufruf übergeben werden muss.

Pagination Script-Vorlagen einfügen

Einige der obigen Beispiele können auch direkt im Step eingefügt werden.

  • Klick auf den Plus-Button im response parsing script
  • dann Tab Spezial-Variablen anwählen
  • dann sehen Sie eine Liste von response parsing script-Vorlagen

Pagination ohne die pagination-Funktion

Es ist auch möglich die nextUrl()-Funktion ohne die pagination() Funktion zu verwenden. Evtl. ist das in einigen komplexen Fällen notwendig, wenn man eigene Logikigen zum Blättern implementieren möchte.

Hier ein Beispiel, wie das aussehen könnte:

<#if (numRowsInJsonResponse > 0 && callcounter < callLimit)>
    <#assign nextPageNumber = (callcounter + 1)>
    ${nextUrl(initialUrl+"&page="+nextPageNumber)}
</#if>

Im Prinzip ist das das, was die pagination()-Funktion im Hintergrund macht.

Hinweis zum page-Parameter

Das Beispiel oben nimmt an, dass die URL in initialUrl noch keinen page Parameter enthält. Deshalb wird dieser angefügt. Würde der Parameter bereits enthalten sein und man wöllte diesen ändern, müsste man sich etwas anderes einfallen lassen. Das ist einer der Dinge, die die pagination()-Funktion z.B. mit .plusOne("page") übernimmt.

Es wird die Variable callcounter verwendet, welche die bisher ausgeführte Anzahl API-Calls enthält.

Hinweis: callcounter startet initial bei 0

Die Variable callLimit entspricht der limit-Variable des APICall Steps und gibt die max. Anzahl auszuführender Calls an. Bitte beim Testen auf kleine Werte z.B. 5 setzen um zu vermeiden. D.h. beim ersten erreichen der nextUrl() Funktion, ist callcounter=1. Deshalb wird nextPageNumber = callcounter + 1 gesetzt, damit der nächste Seitenaufruf page=2 ist.

Die umschließende IF-Bedingung <#if (numRowsInJsonResponse > 0 && callcounter < callLimit)> ist die notwendige Abbruchbedingung, damit keine Endlosschleife entsteht und die nextURL unendlich oft aufgerufen wird. Sie bedeutet ungefähr: "wenn der erste Call Zeilen im Ergebnis hatte (numRowsInJsonResponse > 0), und wir noch innerhalb des CallLimits sind callcounter < callLimit) dann mache weiter und rufe die nextUrl() auf".


Beispiel 2: Hier ein Beispiel für den Inhalt des response parsing scripts. Im Bespiel liefert die Antwort der API eine Liste von Produkten zurück. Durch erneuten Aufruf der URL mit einem zusätzlichen offset Parameter, kann man die nächsten Produkte des Ergebnis abrufen - immer in Blöcken von 150 Produkten.

<#assign row = target.addRow()>
 
<#assign numRows = 0 />
 
<#list json as p>
 <#assign row = target.addRow()>
    ${addColumns(row, p['product'])}
    <#assign numRows = numRows + 1 />
</#list>
 
<#if (numRows > 0 && callcounter < callLimit)>
    <#assign offset = ((callcounter) * 150)>
    ${nextUrl(initialUrl+"&offset="+offset)}
</#if>
 

Der Unterschied zum obigen Beispiel ist, dass der offset Parameter etwas anders berechnet werden muss. Wir gehen hier davon aus, dass 150 Produkte pro Seite kommen und die API statt Seitenzahl (page) den Versatz (offset) bekommen möchte. D.h.
statt page=2 wird offset=150 geschickt (callcounter * 150 entspricht 1 * 150 = 150). Damit weiss die API, dass sie die ersten 150 Produkte überspringen soll. page=3 würde dann mit offset=300 aufgerufen.

Angenommen die erste URL wäre http://api.myshop.com/getproducts, dann würde obiges Script zum Aufruf folgender URLs führen:

  • http://api.myshop.com/getproducts
  • http://api.myshop.com/getproducts&offset=150
  • http://api.myshop.com/getproducts&offset=300
  • http://api.myshop.com/getproducts&offset=450

usw.

In der Programmierung würde das vermutlich einer do-while Schleife entsprechen. D.h. der APICall step versucht immer die von nextUrl() gesetzte URL aufzurufen. Wird nextUrl() nicht aufgerufen, dann wird auch kein weiterer Call gemacht. D.h. die Endbedingung ist hier:

<#if (numRows > 0 && callcounter < callLimit)>

D.h. es muss noch Produkte in der Response geben, und es dürfen nur max. 5-Calls gemacht werden.

Variablen:

Folgende Variablen sind vom Step immer verfügbar

  • host (im Screenshot unten Current URL) - beim ersten Aufruf ist das die URL, die im Feld host steht. Bei jedem weiteren Aufruf kann sich diese verändern, je nach dem was man in die nextUrl()-Funktion übergibt. z.B. einen hochzählenden page-Paramater (z.B. &page=1, &page=2, &page=3 usw.) durch die pagination()-Funktion
  • initialUrl - die initiale URL, also das was in host steht. Sie bleibt bei jedem Aufruf immer gleich - im Gegensatz zu host.
  • callcounter - ein Zähler für die Anzahl calls, die schon gemacht wurden. startet bei 0
  • callLimit - entspricht der limit-Variable des APICall Steps und gibt die max. Anzahl auszuführender Calls an. Bitte beim Testen auf kleine Werte z.B. 5 setzen um zu vermeiden.
  • httpResponseStatusCode: HTTP Status Code der Antwort (Response)
  • httpResponseHeader: eine Map die alle header Werte der Antwort (Response) enthält, z.B. httpResponseHeader['Content-Type']!

Die Variablen und Funktionen können durch das Plus-Zeichen am response parsing script angezeigt werden.

Variablen für nächsten Call setzen

Es gibt Fälle, da will man eine Variable setzen, die aber erst im nächsten Call z.B. requestBody gebraucht wird. Mit andere Worten: Man will sich etwas aus dem ersten Call für den zweiten Call merken.  
Das kann man mit der Funktion setVariable()open in new window erreichen.

${setVariable("myvar", "myvalue")}

Diese Variable ist dann im nächsten Call in allen Freemarker-fähigen Feldern wie requestBody, requestHeaders oder response parsing script über ${getVariable("myvar")} verfügbar.

Beispiel:

Angenommen die Response des 1. Calls liefert einen Token zurück, der im 2. Call per requestBody oder requestHeader übergeben werden muss.

D.h. man muss sich diesen Token im response parsing script aus der response extrahieren und mit setVariable("mytoken", token) merken. Der nächste Call (definiert durch nextUrl()) kann dann auf diese Variable über die Freemarker Template Funktion ${getVariable("mytoken")} zugreifen.

HTTP Error Codes

Im Feld errorStatusCodes können sie konfigurieren welche HTTP Status als Fehler gewertet werden. Standardmäßig (leeres Feld) sind Antworten mit einem HTTP-Statuscode größer oder gleich 300 ein Fehler. Im Falle einer Weiterleitung (Status 3xx) bei einem GET Request wird der Status Code zur weitergeleiteten URL ausgewertet.

Sie können auch individuelle Status als Komma-separierte Liste im Feld angeben. Es ist auch möglich die Status in Form von Regulären Ausdrücken anzugegeben.

Beispiele:

  • leer -> Fehler bei Status >= 300
  • 401,407 -> Fehler bei Status Code 401 & 407
  • 40\[0-7\],5\[0-9\]\[0-9\] -> Fehler bei Status Code 400 bis 407 und 500 bis 599
  • 40\[135\] -> Fehler bei Status Code 401, 403 und 405
  • 3.\*,4.\*,5.\* -> Fehler bei 300 – 599

HTTP Multipart Requests

Eine spezielle Form von Request stellen sog. Multipart Requests (siehe hieropen in new window). Dabei werden mehrere verschiedene Daten in einem Request kombiniert gesendet (z.B. eine Datei zusammen mit einem JSON-Body). Ein typisches Beispiel sind Webformulare im Browser, wo mehrere Formularfelder und ein Datei-Upload übertragen werden (z.B. Kontaktformular mit Dateiupload). Diese werden in der Regel als Multipart-Request realisiert.
Einige APIs, die man anbindet verlangen manchmal auch den ContenType=multipart/form-data; charset=ISO-8859-1

Alle HTTP-Steps APICall, SpreadsheetURLDownload und URLDownload unterstützen HTTP Multipart-Requests.

Dazu muss die Option bodyContentType auf multipart/form-data gestellt werden.

Bei bodyContentType=multipart/form-data kann man die Form-Parameter übermitteln z.B. &param1=value1&param2=value2 (wenn die URL/host keine Parameter hat, dann ohne das erste &-Zeichen). Es ist auch für bessere Lesbarkeit möglich, pro Zeile einen Parameter zu verwenden. Wichtig ist, dass der Wert keine Zeilenumbrüche beinhaltet. Für mehrzeilige Werte sollte die Freemarker-Funktion <#compress>open in new window probiert werden, die automatisch überflüssige Leerzeichen und Zeilenumbrücke entfernt.

Ergebnis:

Ein POST-Request z.B. an ein Tool wie RequestBin sähe damit aus wie folgt:

RequestBody bei Multipart

Das man bei bodyContentType=multipart/form-data das Feld requestBody auf diese Art mit mehreren Parametern befüllen kann ist eine Besonderheit von Multipart Requests. Für alle anderen Werte bei bodyContentType wird der RequestBody nicht derart interpretiert sondern so übermittelt wie er ist. D.h. will man z.B. ein JSON-Dokument als ganzes übermitteln (wie es bei 90% aller REST-APIs der Fall ist), dann beinhaltet der requestBody nur das JSON.

Unterschied zum SpreadsheetURLDownload

Auch der SpreadsheetURLDownload ist in der Lage mehrere URLs aufzurufen. Der Unterschied ist, dass die URLs beim SpreadsheetURLDownload da schon vorher feststehen und als Spreadsheet hinein gegeben werden - mit einer URL pro Zeile.

Der APICall ist dahingehend dynamisch. Es ist vorher nicht bekannt, wieviele URLs / Calls aufgerufen werden, da es von der Antwort abhängt, ob der Aufruf der nächsten URL (nextUrl()) stattfindet oder nicht.