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
| Feld | Typ | Pflicht | Hinweise |
|---|---|---|---|
logicalName | string | ja | Mit Herausgeberpräfix, Snake-Case in Kleinbuchstaben. Über Versionen hinweg stabil. |
displayName | string | ja | Die Zusammenfassung (summary) der OpenAPI-Operation. |
publisherPrefix | string | ja | Zugleich ein Segment des ausgelieferten Pfads. |
version | integer | ja | 1–999. Wird als Pfadsegment v{version} ausgeliefert. |
route | string | ja | Unterhalb des Versionssegments, ohne führenden Schrägstrich. Ein Segment in geschweiften Klammern ist ein Pfadparameter. |
verb | string | ja | Get, Post, Put, Patch, Delete. |
script | string | ja | Der BS-Lang-Code, den diese API ausführt. Es gibt genau eine Implementierung, und das ist sie. |
requiredScope | string | ja | Der Scope, den die App-Registrierung eines Aufrufers besitzen muss. |
description | string | null | nein | Die Beschreibung der OpenAPI-Operation. |
requestSchema | object | null | nein | Ein 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). |
responseSchema | object | null | nein | Ein JSON Schema, das im OpenAPI-Dokument veröffentlicht wird. |
responseContentType | string | nein | application/json, text/plain oder text/html – womit der Vertrag normalerweise antwortet und was die generierte OpenAPI-Beschreibung veröffentlicht. |
isEnabled | boolean | nein | Eine 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
responseContentTypeist einer, den die Plattform ausliefern kann. - Eine Route für
Read,UpdateoderDeletedeklariert{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.