Direkt zum Hauptinhalt

Definition einer benutzerdefinierten API

Schema-ID: custom-api@1 Maschinenlesbares Schema: custom-api.schema.json

Die maßgebliche Definition einer benutzerdefinierten API (Abschnitt 6.9): ein versionierter HTTP-Vertrag, den eine Umgebung für Systeme außerhalb der Plattform veröffentlicht, der einer Lösung gehört und in deren Paket mitreist. Eine Definition wird identisch validiert, ob sie aus dem Admin Control Center, einem API-Aufruf, einem KI-Assistenten oder einem Lösungsimport stammt.

Aufbau​

FeldTypPflichtHinweise
logicalNamestringjaMit Herausgeberpräfix, Snake-Case in Kleinbuchstaben. Über Versionen hinweg stabil.
displayNamestringjaDie Zusammenfassung (summary) der OpenAPI-Operation.
publisherPrefixstringjaZugleich ein Segment des ausgelieferten Pfads.
versionintegerja1–999. Wird als Pfadsegment v{version} ausgeliefert.
routestringjaUnterhalb des Versionssegments, ohne führenden Schrägstrich. Ein Segment in geschweiften Klammern ist ein Pfadparameter.
verbstringjaGet, Post, Put, Patch, Delete.
scriptstringjaDer BS-Lang-Code, den diese API ausführt. Es gibt genau eine Implementierung, und das ist sie.
requiredScopestringjaDer Scope, den die App-Registrierung eines Aufrufers besitzen muss.
descriptionstring | nullneinDie Beschreibung der OpenAPI-Operation.
requestSchemaobject | nullneinEin JSON Schema, bei jedem Aufruf geprüft. Für ein Verb ohne Anfragetext abgelehnt. Null heißt: die getInput-Namen des Skripts sind der Vertrag (im OpenAPI-Dokument veröffentlicht; bei einem Portalaufruf eine geschlossene Liste).
responseSchemaobject | nullneinEin JSON Schema, das im OpenAPI-Dokument veröffentlicht wird.
responseContentTypestringneinapplication/json, text/plain oder text/html – womit der Vertrag normalerweise antwortet und was die generierte OpenAPI-Beschreibung veröffentlicht.
isEnabledbooleanneinEine deaktivierte Definition wird gespeichert und exportiert und liefert 404.

Die Identität ist ein Paar​

(logicalName, version) identifiziert die API; (publisherPrefix, version, verb, route) ist ihre Adresse. par_approve_invoice v1 und v2 sind ein Vertrag in zwei Lebensaltern und antworten nebeneinander, während ein Integrationspartner migriert – eine zweite Definition mit demselben Namen und derselben Version ist also ein Duplikat, eine mit demselben Namen und einer neuen Version dagegen die nächste Version. Zwei Definitionen dürfen sich niemals eine Adresse teilen, wie auch immer sie heißen.

Der ausgelieferte Pfad​

/api/{publisherPrefix}/v{version}/{route}

Heute erreichbar unterhalb einer Umgebung – /environments/{environmentId}/api/par/v1/invoices –, weil ein Client-Credentials-Token einen Mandanten nennt und nicht eine seiner Umgebungen. Die kürzere öffentliche Form kommt mit dem Gateway, das einen Hostnamen einer Umgebung zuordnet.

Was ausgeführt wird​

Eine einzige Sache: das script. Eine Definition nennt eine Route und den BS-Lang-Code, der sie beantwortet, und es gibt sonst nichts zu wählen. Das Skript liest die Anfrage mit getInput, antwortet mit setOutput oder setBody, wählt Status und Inhaltstyp mit setStatus und setContentType, setzt Header mit setHeader, erreicht andere Tabellen über die Datensatzfunktionen und lehnt mit throwValidationError ab.

Früher wählte eine Definition zwischen vier Implementierungsarten – einer direkten Tabellenoperation, einem Automation Flow, einer Function und einer benutzerdefinierten Plug-in-Nachricht. Drei davon ließen sich definieren, aber nie ausführen, sodass ein Aufruf mit 501 antwortete und die Iteration nannte, auf die er wartete; nur die vierte lief. Das ist vorbei. Ein Vertrag, dessen Verhalten der eigene Code des Erstellers ist, lässt sich einfacher erklären und einfacher versionieren, und er war die einzige der vier Arten, die etwas ausdrücken konnte, das die Plattform nicht vorhergesehen hatte – und es gibt kein 501 mehr, weil keine unverdrahtete Art mehr übrig ist, die eines erzeugen könnte.

Logik, die zwei APIs gemeinsam nutzen sollen, ist ein Lösungsskript, das per Name importiert wird, keine fünfte Art.

Regeln, die ein Schema nicht ausdrücken kann​

Beim Speichern der Definition zusätzlich zum obigen Schema geprüft:

  • Der deklarierte responseContentType ist einer, den die Plattform ausliefern kann.
  • Eine Route für Read, Update oder Delete deklariert {id} – nur von dort kann die ID des Datensatzes kommen.
  • Ein Verb ohne Anfragetext hat kein Anfrageschema. Ein Verb mit Anfragetext darf es null lassen.
  • Anfrage- und Antwortschema lassen sich als JSON Schema kompilieren.
  • Das Skript lässt sich als BS Lang parsen, und jedes Modul, das es importiert, wird zu einem Lösungsskript aufgelöst.

Beispiel​

{
"logicalName": "par_create_invoice",
"displayName": "Create an invoice",
"publisherPrefix": "par",
"description": "Creates an invoice from a validated body.",
"version": 1,
"route": "invoices",
"verb": "Post",
"requestSchema": {
"type": "object",
"additionalProperties": false,
"required": ["par_name", "par_amount"],
"properties": {
"par_name": { "type": "string", "maxLength": 200 },
"par_amount": { "type": "number", "exclusiveMinimum": 0 }
}
},
"responseContentType": "application/json",
"requiredScope": "modelapps.write",
"isEnabled": true,
"script": "var id = createRecord('par_invoice', {
par_name: getInput('par_name'),
par_amount: getInput('par_amount')
});
setOutput('id', id);"
}

Der Anfragetext wird vor der Ausführung des Skripts gegen requestSchema geprüft, sodass getInput eine bereits validierte Form liest, statt einer, gegen die sich jedes Skript selbst absichern muss.

Eine Eingabe kann ein Objekt oder eine Liste sein​

getInput übergibt dem Skript, was auch immer die Eigenschaft enthält – eine Zeichenfolge, eine Zahl, einen booleschen Wert oder ein vollständig übertragenes Objekt bzw. Array, das ein Skript über Member (f.fileName) und length() durchläuft. Ein Vertrag, der in seinem requestSchema eine strukturierte Eingabe beschreibt, kann eine solche also auch tatsächlich lesen:

"par_document": {
"type": "object",
"additionalProperties": false,
"required": ["fileName", "contentType", "contentBase64"],
"properties": {
"fileName": { "type": "string", "maxLength": 260 },
"contentType": { "type": "string", "maxLength": 200 },
"sizeBytes": { "type": "integer" },
"contentBase64": { "type": "string", "maxLength": 5600000 }
}
}

Bis zur Portal-Runde 10 kam ein Objekt als roher JSON-Text an, den BS Lang nicht parsen kann – ein Ersteller, der die gewünschte Form deklarierte, war also schlechter gestellt als einer, der sie in lose Eigenschaften zerlegte, und das ist das Gegenteil dessen, wofür ein Schema da ist. Die Verschachtelungstiefe ist durch die eigene Grenze des JSON-Parsers von 64 Ebenen beschränkt, bevor irgendetwas hiervon erreicht wird.

Was sie niemals tut​

Eine benutzerdefinierte API erweitert keinen Zugriff. Die Datensatzfunktionen des Skripts gehen durch dieselbe Tür wie die eigenen Bildschirme der Plattform und laufen als der Aufrufer; dessen Berechtigung auf einer Tabelle und die dahinterliegende Datenrichtlinie entscheiden also, was zurückkommt. Ein kuratierter Vertrag gibt einem externen System eine stabile Sicht, niemals eine umfassendere.