Requêtes HTTP
Placez un credential là où le fournisseur l'attend, envoyez un corps de requête POST et fixez le format de réponse — la moitié « requête » d'une source de données déclarative en mode poll
Le bloc request d'une source de données déclarative en mode poll décrit l'appel que la plateforme effectue à chaque tick. url, method, headers et query en sont les parties évidentes. Cette page couvre les trois champs qui déterminent si une véritable API fournisseur fonctionne ou non :
request.auth— où est placé le credential désigné parauthRef: un token bearer, un en-tête personnalisé, ou un paramètre de requête.request.body— le payload d'unPOST, GraphQL compris.response.format— une indication de décodage explicite (json,xml,csv,jsonl) lorsqu'un fournisseur étiquette mal son propre payload.
Ces trois champs sont optionnels. Un manifeste écrit avant leur existence est analysé et se comporte exactement comme auparavant : authRef seul signifie Authorization: Bearer <apiKey>, et le format de réponse est détecté à partir des signaux du payload lui-même.
Où part la requête — url, ou l'endpoint de la connexion
Une requête désigne son endpoint exactement une fois :
url— une URLhttps://fixe, identique pour chaque organisation qui installe l'extension.endpointRef+path— l'URL de base vient de la connexion créée par un administrateur :endpointRefest la clé du champ de configuration{ type: 'url', purpose: 'endpoint' }de l'extension, etpathy est ajouté.
request: {
endpointRef: 'baseUrl',
path: '/api/states',
authRef: 'token',
auth: { placement: 'bearer' },
}Un chemin déjà présent dans l'URL de la connexion est conservé (https://proxy.example.com/ha + /api/states). path commence par / et ne porte ni query string, ni fragment, ni .. — les paramètres statiques vont dans query, comme d'habitude. Tout ce qui suit s'applique aux deux formes. Voir Endpoints fournis par une connexion pour déclarer le champ et ce que saisit un administrateur.
request.auth — où va le credential
authRef indique quel credential stocké utiliser. auth indique où le placer. Définir auth sans authRef est une erreur de validation — il n'y aurait aucun credential à placer.
placement | Ce que la plateforme envoie | Champs supplémentaires |
|---|---|---|
bearer | Authorization: Bearer <secret> | credential |
header | <name>: <prefix><secret> | name (requis), prefix, credential |
query | ?<name>=<secret> | name (requis), credential |
Quel champ de credential
credential désigne un champ de la connexion que la plateforme a résolue pour vous — jamais une valeur secrète. Chaque placement porte un seul secret opaque : il n'y a donc exactement que deux champs possibles :
| Champ | Origine typique |
|---|---|
apiKey | Une connexion à clé API statique |
accessToken | Le token d'accès courant d'une connexion OAuth |
Ces deux champs constituent le vocabulaire parce qu'ils sont ce à quoi une connexion de source de données se résout réellement. Une connexion OAuth ne transmet que le token d'accès — le matériel de renouvellement ne doit jamais parvenir à une source. Tout autre nom de champ est une erreur au build, plutôt qu'un manifeste qui valide puis échoue à chaque tick.
Bearer (le comportement par défaut)
Omettez complètement auth et vous obtenez ceci. Ne l'écrivez explicitement que pour choisir un autre champ :
request: {
url: 'https://api.example.com/v1/records',
authRef: 'example-api-key',
auth: { placement: 'bearer', credential: 'accessToken' },
}Un en-tête personnalisé
Le cas courant x-api-key. prefix est écrit tel quel devant le secret — incluez l'espace final si le fournisseur l'attend :
request: {
url: 'https://api.example.com/v1/records',
authRef: 'example-api-key',
auth: { placement: 'header', name: 'x-api-key' },
}name ne peut pas valoir Authorization, quelle que soit la casse. placement: 'bearer' est la seule manière de définir cet en-tête, ce qui garantit qu'un seul chemin de code peut le produire. Une entrée Authorization en clair dans request.headers est rejetée pour la même raison.
Un paramètre de requête
Certaines API anciennes attendent la clé dans l'URL :
request: {
url: 'https://api.example.com/v1/records',
query: { limit: '500' },
authRef: 'example-api-key',
auth: { placement: 'query', name: 'api_key' },
}name doit n'utiliser que des caractères non réservés d'URL (lettres, chiffres, ., _, ~, -) et ne peut pas entrer en collision avec une clé déjà présente dans request.query ni avec un paramètre déjà écrit dans url (?api_key=...) — le credential écraserait silencieusement votre paramètre statique, exactement le genre de bug que l'on découvre en production. Renommez l'un des deux.
Préférez bearer ou header lorsque le fournisseur accepte l'un des deux. Un credential dans une chaîne de requête transite par les journaux des proxys, les en-têtes Referer des navigateurs et les journaux d'accès des CDN, ce qui n'est pas le cas d'un en-tête. Scrydon le masque partout où il écrit l'URL (voir Les credentials n'atteignent jamais un journal) — il ne peut pas masquer les journaux du fournisseur.
Il n'existe pas de placement basic. HTTP Basic requiert une paire nom d'utilisateur / mot de passe, et aucun type de connexion de source de données n'en résout aujourd'hui — un placement basic validerait au build puis échouerait à chaque tick. Il reviendra en même temps que la résolution des connexions basicAuth qui renseignera cette paire. Pour une API qui ne parle que Basic, utilisez header avec un prefix une fois ce type de connexion disponible.
request.body — payloads POST
Un corps de requête n'est valide que si method vaut 'POST'. Un GET accompagné d'un corps est rejeté au moment du build plutôt qu'envoyé puis ignoré silencieusement.
JSON
request: {
url: 'https://api.example.com/v1/search',
method: 'POST',
body: { kind: 'json', value: { since: '2026-01-01', pageSize: 500 } },
}value accepte n'importe quelle valeur JSON — objet, tableau, chaîne, nombre, booléen ou null. Elle est sérialisée avec JSON.stringify et envoyée avec Content-Type: application/json.
GraphQL
GraphQL n'est pas un kind distinct. C'est un corps JSON dont la valeur porte query et variables :
ingest: {
mode: 'poll',
intervalSec: 300,
request: {
url: 'https://api.example.com/graphql',
method: 'POST',
authRef: 'example-api-key',
body: {
kind: 'json',
value: {
query: 'query Vessels($limit: Int!) { vessels(limit: $limit) { mmsi lat lon } }',
variables: { limit: 500 },
},
},
},
response: { itemsPath: '$.data.vessels' },
mapping: {
mmsi: { path: 'mmsi' },
latitude: { path: 'lat' },
longitude: { path: 'lon' },
},
}Notez qu'itemsPath traverse l'enveloppe data de GraphQL. Une réponse GraphQL en erreur ne comporte pas de tableau data.vessels : le tick échoue donc bruyamment au lieu d'écrire zéro ligne.
Texte
Pour tout ce qui n'est pas du JSON. contentType est requis — rien ne permet de le déduire :
request: {
url: 'https://api.example.com/v1/query',
method: 'POST',
body: {
kind: 'text',
value: 'from(bucket:"telemetry") |> range(start:-5m)',
contentType: 'application/vnd.flux',
},
}Un Content-Type que vous définissez vous-même dans request.headers l'emporte toujours. Le kind du corps n'en fournit un que si vous ne l'avez pas fait — application/json pour kind: 'json', et le contentType déclaré pour kind: 'text'.
response.format — fixer le décodage
Par défaut, la plateforme détecte le format du corps à partir du Content-Type de la réponse, puis de l'extension de l'URL, puis du premier octet non blanc. C'est format: 'auto', et c'est ce que vous obtenez si vous ne dites rien.
Définissez format lorsqu'un fournisseur se trompe sur son propre payload — un export CSV servi en application/octet-stream, un flux JSONL étiqueté text/plain, ou un flux RSS servi en application/json :
response: { itemsPath: '$', format: 'csv' }| Valeur | Signification |
|---|---|
auto | Détection via Content-Type → extension d'URL → premier octet (valeur par défaut) |
json | Un document JSON ; itemsPath y sélectionne le tableau de lignes |
xml | Un document XML — RSS, Atom ou tout autre dialecte ; itemsPath y sélectionne les entrées (rss.channel.item, feed.entry) |
csv | Une ligne d'en-tête puis les lignes de données ; itemsPath vaut '$' |
jsonl | Un objet JSON par ligne ; itemsPath vaut '$' |
Il n'existe délibérément aucune valeur rss ni atom : les trois s'analysent de façon identique, et la seule différence entre elles est le chemin d'entrée que votre itemsPath nomme déjà. Voir Formats de réponse pour la présentation complète des flux.
Un format explicite est une instruction de décodage, pas une suggestion : si le corps ne peut pas être lu ainsi, le tick échoue avec une erreur d'analyse au lieu de tenter une seconde hypothèse.
Les credentials n'atteignent jamais un journal
Un credential résolu pour un tick n'existe que le temps de cette requête.
- Le manifeste porte des noms de champs, jamais des valeurs.
.strict()sur le blocrequestrejette tout champ superflu, et un en-têteAuthorizationen clair est rejeté d'office. - Avec
placement: 'query', le secret se trouve nécessairement dans l'URL. Chaque URL que la plateforme écrit dans une ligne de journal ou un message d'erreur voit ce paramètre remplacé par***; les paramètres statiques qui l'entourent sont conservés, de sorte que le journal indique toujours quel appel a échoué. - Une erreur levée parce qu'une connexion ne comporte pas le champ que vous avez désigné indique quel champ était absent (
credentials.accessToken), jamais le contenu d'un autre champ.
Cela protège les journaux et les surfaces d'erreur de Scrydon. Cela ne peut pas protéger ceux du fournisseur — une raison de plus de préférer bearer ou header à query.
Récapitulatif de la validation
Ce sont des erreurs détectées au build, par extension build, avant qu'une archive ne soit jamais téléversée :
| Rejeté | Raison |
|---|---|
url et endpointRef/path à la fois, ou aucun des deux | Une requête désigne son endpoint exactement une fois |
endpointRef sans path, ou path sans endpointRef | L'un exige l'autre |
Un path qui ne commence pas par /, commence par //, ou porte ?, #, .., %2e, %2f, %5c, une barre oblique inverse ou une espace | Le chemin ne peut pas faire sortir la requête de l'URL de la connexion |
endpointRef ne désignant aucun champ de configuration { type: 'url', purpose: 'endpoint' } de l'extension | Il n'y a aucun endpoint que la connexion puisse fournir |
authRef ne désignant aucun identifiant de auth.credentials de l'extension | Il n'y a aucun identifiant que la connexion puisse porter |
auth sans authRef | Aucun credential n'est désigné, il n'y a donc rien à placer |
auth.name = Authorization (toute casse) | placement: 'bearer' est la seule manière de définir cet en-tête |
auth.name en collision avec une clé de query ou un paramètre de url | Le credential écraserait silencieusement un paramètre statique |
credential désignant autre chose que apiKey ou accessToken | Un placement porte un seul secret opaque, et ce sont les deux seuls champs qu'une connexion résout |
placement: 'basic' | Aucun type de connexion ne résout encore une paire nom d'utilisateur / mot de passe |
body sans method: 'POST' | Le corps d'un GET n'est jamais envoyé |
kind: 'text' sans contentType | Rien ne permet de le déduire |
Tout champ non listé dans request, auth ou body | Ces blocs sont .strict() — une faute de frappe est une erreur, pas un silence |