Aller au contenu principal

Définition d’API personnalisée

Identifiant du schéma : custom-api@1 Schéma lisible par machine : custom-api.schema.json

La définition canonique d’une API personnalisée (section 6.9) : un contrat HTTP versionné qu’un environnement publie à destination de systèmes extérieurs à la plateforme, détenu par une solution et transporté dans son package. Une définition est validée de manière identique, qu’elle provienne du Centre de contrôle d’administration, d’un appel d’API, d’un assistant IA ou d’une importation de solution.

Structure​

ChampTypeObligatoireRemarques
logicalNamestringouiPréfixé par l’éditeur, en snake-case minuscule. Stable d’une version à l’autre.
displayNamestringouiLe résumé de l’opération OpenAPI.
publisherPrefixstringouiÉgalement un segment du chemin servi.
versionintegeroui1–999. Servi comme segment de chemin v{version}.
routestringouiSous le segment de version, sans barre oblique initiale. Un segment entre accolades est un paramètre de chemin.
verbstringouiGet, Post, Put, Patch, Delete.
scriptstringouiLe BS Lang qu’exécute cette API. Il n’y a qu’une implémentation, et c’est celle-ci.
requiredScopestringouiLa portée que doit détenir l’inscription d’application de l’appelant.
descriptionstring | nullnonLa description de l’opération OpenAPI.
requestSchemaobject | nullnonUn JSON Schema, vérifié à chaque appel. Refusé pour un verbe sans corps. Null signifie que les noms getInput du script sont le contrat (publié dans OpenAPI ; lors d’un appel de portail, une liste fermée).
responseSchemaobject | nullnonUn JSON Schema, publié dans le document OpenAPI.
responseContentTypestringnonapplication/json, text/plain ou text/html — ce par quoi le contrat répond normalement, et ce que publie l’OpenAPI généré.
isEnabledbooleannonUne définition désactivée est stockée et exportée, et renvoie 404.

L’identité est une paire​

(logicalName, version) identifie l’API ; (publisherPrefix, version, verb, route) est son adresse. par_approve_invoice v1 et v2 sont un même contrat à deux âges, qui répondent côte à côte pendant qu’un partenaire d’intégration migre — une seconde définition partageant le nom et la version est donc un doublon, alors qu’une définition partageant le nom avec une nouvelle version est la version suivante. Deux définitions ne peuvent jamais partager une adresse, quel que soit leur nom.

Le chemin servi​

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

Accessible aujourd’hui sous un environnement — /environments/{environmentId}/api/par/v1/invoices — parce qu’un jeton client-credentials désigne un locataire plutôt que l’un de ses environnements. La forme publique plus courte arrivera avec la Gateway, qui associe un nom d’hôte à un environnement.

Ce qui s’exécute​

Une seule chose : le script. Une définition nomme une route et le BS Lang qui y répond, et il n’y a rien d’autre à choisir. Il lit la requête avec getInput, répond avec setOutput ou setBody, choisit un statut et un type de contenu avec setStatus et setContentType, définit des en-têtes avec setHeader, accède à d’autres tables avec les fonctions d’enregistrement, et refuse avec throwValidationError.

Auparavant, il fallait choisir entre quatre types d’implémentation — une opération directe sur une table, un flux d’automatisation, une fonction et un message de plug-in personnalisé. Trois d’entre eux pouvaient être définis mais jamais distribués : un appel répondait donc 501 en nommant l’itération attendue, et seul le quatrième s’exécutait. C’est terminé. Un contrat dont le comportement est le propre code du créateur est plus simple à expliquer, plus simple à versionner, et c’était le seul des quatre capable d’exprimer quelque chose que la plateforme n’avait pas prévu — et il n’y a plus de 501, puisqu’il ne reste aucun type non raccordé pour en produire un.

Une logique à partager entre deux API est un script de solution, importé par son nom, et non un cinquième type.

Règles qu’un schéma ne peut pas exprimer​

Vérifiées à l’enregistrement de la définition, en plus du schéma ci-dessus :

  • Le responseContentType déclaré est un type que la plateforme peut servir.
  • Une route Read, Update ou Delete déclare {id} — c’est le seul endroit d’où l’id de l’enregistrement peut provenir.
  • Un verbe sans corps n’a pas de schéma de requête. Un verbe avec corps peut le laisser à null.
  • Les schémas de requête et de réponse se compilent en JSON Schema.
  • Le script s’analyse comme du BS Lang, et chaque module qu’il importe se résout en un script de solution.

Exemple​

{
"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);"
}

Le corps est vérifié selon requestSchema avant l’exécution du script : getInput lit donc une forme déjà validée, plutôt qu’une forme contre laquelle chaque script devrait se défendre lui-même.

Une entrée peut être un objet ou une liste​

getInput remet au script ce que contient la propriété — une chaîne, un nombre, un booléen, ou un objet ou un tableau transmis en entier, que le script parcourt par membre (f.fileName) et avec length(). Un contrat qui décrit une entrée structurée dans son requestSchema peut donc réellement la lire :

"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 }
}
}

Jusqu’à la dixième série de corrections des portails, un objet arrivait sous forme de texte JSON brut, que BS Lang n’a aucun moyen d’analyser — un créateur qui déclarait la forme voulue était donc moins bien loti que celui qui l’aplatissait en propriétés isolées, soit l’inverse de ce à quoi sert un schéma. L’imbrication est limitée par la limite de 64 niveaux propre à l’analyseur JSON, avant même que tout ceci n’entre en jeu.

Ce qu’elle ne fait jamais​

Une API personnalisée n’élargit pas l’accès. Les fonctions d’enregistrement du script passent par la même porte que les propres écrans de la plateforme et s’exécutent en tant qu’appelant : ce sont ses autorisations sur une table, et la politique de données qui s’y applique, qui décident de ce qui est renvoyé. Un contrat soigné offre à un système extérieur une vue stable, jamais une vue plus large.