Aller au contenu

Standard ATSS

Afro Tools Spec Standard — le format lisible par les machines qui alimente le registre. Version 1.1.

GitHub

Traduction du standard : en cas de différence, la version anglaise fait foi. Les exemples de code restent tels quels.

Qu’est-ce que l’ATSS

L’ATSS (Afro.tools Spec Specification) définit la structure, les champs obligatoires et les règles de chaque spec de ce registre.

Une spec est une description structurée et lisible par les machines d’une capacité d’API. Ce n’est pas un wrapper. Ce n’est pas un SDK. C’est une description déclarative.

Note de terminologie : dans ce dépôt, « spec » désigne une description d’API ATSS (schema.json + canonical_example.ts). À ne pas confondre avec les « skills » de Claude Code (fichiers SKILL.md dans plugin/skills/), qui sont des fichiers d’instructions pour les agents IA.


Structure des dossiers

specs/{category}/{provider_slug}/
├── provider.json              ← provider-level metadata
└── {capability}/
    ├── schema.json            ← capability spec
    └── canonical_example.ts   ← working TypeScript example

category — payment, communications, identity, logistics ou language (extensible à d’autres catégories). communications couvre les fournisseurs qui envoient des SMS, WhatsApp, USSD, voix ou e-mail — les canaux précis vont dans le champ channels de provider.json, pas dans le nom de la catégorie. provider_slug — minuscules, sans espaces, sans tirets (ex. paycard, wave, nimbasms). Unique toutes catégories confondues : le registre, le MCP et le site cherchent les fournisseurs par leur slug seul, donc un fournisseur proposant des produits dans plusieurs catégories utilise un slug par produit. validate.js rejette un slug réutilisé dans une autre catégorie. capability — verbe en snake_case (ex. create_payment, verify_payment, send_otp)

Chaque dossier de capacité doit contenir exactement ces deux fichiers. Rien d’autre.


provider.json : champs obligatoires

Un provider.json par dossier de fournisseur. Il porte les métadonnées du fournisseur, migrées depuis schema.json.

{
  "slug": "paycard",
  "name": "Paycard",
  "category": "payment",
  "country_code": ["GN"],
  "description": "Paycard is a Guinean mobile money payment gateway that lets you accept payments via MTN, Orange, and Cellcom mobile wallets.",
  "example_prompt": "Integrate Paycard to accept mobile money payments in Guinea. Create a payment, extract the operation_reference, and verify the transaction server-side before fulfilling the order."
}
ChampTypeRègle
slugstringMinuscules, sans espaces, sans tirets — correspond au nom du dossier
namestringNom d’affichage du fournisseur
categorystring"payment", "communications", "identity", "logistics" ou "language" — chaque catégorie a un plugin/skills/{category}/SKILL.md correspondant
channelsstring[]Obligatoire quand category vaut "communications", facultatif sinon. Tableau non vide, valeurs parmi sms, whatsapp, ussd, voice, email. Liste les canaux décrits dans la documentation publique du fournisseur — pas seulement ceux qui ont déjà une spec
country_codestring[]Codes ISO 3166-1 alpha-2 — tableau non vide
descriptionstringDescription du fournisseur en une phrase, pour l’affichage et le contexte MCP
example_promptstringPrompt d’usage de bout en bout pour les agents IA — décrit un scénario d’intégration complet

schema.json : champs obligatoires

{
  "spec_version": "1.0",
  "provider_api_version": "2024-01-01",
  "capability": "create_payment",
  "capability_type": "synchronous",
  "status": "ready",
  "currency": ["GNF"],
  "sandbox": false,
  "docs_url": "https://paycard.com/docs/create-payment",
  "docs_public": false,
  "auth": {
    "type": "api_key",
    "location": "body",
    "field": "c",
    "format": "{token}",
    "env_var": "PAYCARD_API_KEY"
  },
  "endpoint": {
    "method": "POST",
    "url": "https://mapaycard.com/epay/create/"
  },
  "example_prompt": "Initiate a Paycard mobile money payment for a given amount in GNF, extract the operation_reference from the response, and store it immediately before redirecting the user.",
  "input_schema": {},
  "response_schema": {},
  "error_schema": {},
  "gotchas": [
    "Always verify payment status server-side before fulfilling an order."
  ]
}

Règles des champs

ChampTypeRègle
spec_versionstring"1.0", ou "1.1" quand la spec utilise un bloc ajouté en 1.1 (voir § Blocs ajoutés dans l’ATSS 1.1)
provider_api_versionstringYYYY-MM-DD si le fournisseur n’a pas de numéro de version
capabilitystringsnake_case — correspond au nom du dossier
capability_typeenumsynchronous, asynchronous, webhook ou sdk
statusenumunverified, draft, ready, verified, deprecated, archived
currencystring[]Codes ISO 4217. Obligatoire quand la category du fournisseur est "payment" (et doit correspondre à son country_code — voir COUNTRY_CURRENCY_MAP dans scripts/validate.js ; un code d’avant redénomination que le fournisseur utilise encore, comme SLL, est accepté via LEGACY_CURRENCY_CODES) ; facultatif sinon — l’omettre pour identity, communications, etc.
sandboxbooleantrue si le fournisseur a un environnement sandbox
docs_urlstringPeut valoir "" — le fournisseur n’a pas d’URL de documentation publique
docs_publicbooleanfalse si le fournisseur ne publie pas sa documentation d’API publiquement
authobjectDescripteur d’authentification — voir les variantes d’auth ci-dessous
endpointobject{ method, url }
example_promptstringObligatoire pour ready/verified — décrit l’usage de cette capacité précise pour les agents IA
input_schemaobjectJSON Schema décrivant le corps de la requête. Les paramètres de chemin, de requête et d’en-tête vont dans parameters (1.1)
response_schemaobjectJSON Schema décrivant la réponse en cas de succès
error_schemaobjectJSON Schema décrivant la réponse en cas d’erreur
gotchasstring[]OBLIGATOIRE — au moins 1 entrée, sans exception

Champs absents de schema.json (migrés vers provider.json) : provider_slug, provider_name, category, country_code.

evidence : obligatoire seulement quand status vaut "unverified"

"evidence": {
  "source_url": "https://provider.com/docs/create-payment",
  "source_reference": "Section 'Create a payment'",
  "extracted_at": "2026-09-15T10:00:00Z",
  "snapshot_hash": "a1b2c3…",
  "spans": ["POST /v1/payments", "Authorization: Bearer {api_key}"]
}

Chaque entrée de spans[] doit apparaître littéralement (espaces et casse normalisés) dans le document source situé à source_url. C’est ce qui permet à un relecteur de vérifier qu’une spec unverified n’a pas été hallucinée, sans relire toute la documentation du fournisseur depuis zéro.

Blocs ajoutés dans l’ATSS 1.1

Facultatifs, vérifiés avec atss.schema.json par npm run validate. Une spec qui en utilise un met spec_version: "1.1" ; une spec 1.0 reste valide telle quelle.

"verification": {                        // required when status is "verified", forbidden otherwise
  "method": "test_live",                 // test_live | app | community_report
  "environment": "sandbox",              // sandbox | production
  "at": "2026-10-24",                    // date of the test
  "ref": "https://…",                    // the run, the app, or the report issue — required for community_report
  "spec_hash": "sha256:…"                // what was tested: schema.json without this block + canonical_example.ts
},
"open_questions": [                      // what the provider's docs do not answer — never filled with a guess
  "Does the payment URL embed the public or the secret key?"
]
ChampRègle
verification.methodtest_live — npm run test:live a réussi ; app — une intégration qui fonctionne (afrotools/examples ou une app en usage) ; community_report — un rapport d’intégration qu’un mainteneur a accepté
verification.environmentOù cela a été testé. sandbox prouve la forme des requêtes et des réponses, pas le comportement en production (prérequis, limites, argent réel)
verification.refURL de l’exécution, du code de l’app ou du rapport. Pour community_report, l’issue d’afrotools/afrotools
verification.spec_hashvalidate le recalcule : une spec vérifiée puis modifiée échoue jusqu’à ce qu’elle soit vérifiée de nouveau, ou qu’un mainteneur ayant contrôlé la modification mette le hash à jour (le message d’erreur affiche la nouvelle valeur)
open_questionsChaînes non vides. Un agent doit en informer l’utilisateur avant de s’appuyer sur la spec

Auth à plusieurs clés — des champs ajoutés à auth, à côté des champs 1.0 (qui restent) :

"auth": {
  "type": "bearer", "location": "header", "field": "Authorization", "format": "Bearer {access_token}", "env_var": "DJOMY_CLIENT_ID",
  "credentials": [                       // every secret the user must provide
    { "env_var": "DJOMY_CLIENT_ID", "description": "clientId (API key), provided in the Djomy merchant space" },
    { "env_var": "DJOMY_CLIENT_SECRET", "description": "clientSecret (secret key), provided in the Djomy merchant space" }
  ],
  "signing": {                           // produces {signature}
    "algorithm": "HMAC-SHA256", "key": "{DJOMY_CLIENT_SECRET}", "message": "{DJOMY_CLIENT_ID}", "encoding": "hex"
  },
  "token_exchange": {                    // produces {access_token}
    "method": "POST", "path": "/v1/auth",
    "headers": [{ "in": "header", "name": "X-API-KEY", "value": "{DJOMY_CLIENT_ID}:{signature}" }],
    "token_field": "data.accessToken",   // dotted path in the response body
    "expires_in_s": 3600
  },
  "apply": [                             // what every request of this capability sends
    { "in": "header", "name": "X-API-KEY", "value": "{DJOMY_CLIENT_ID}:{signature}" },
    { "in": "header", "name": "Authorization", "value": "Bearer {access_token}" }
  ]
}

Signature de webhook — uniquement sur une spec capability_type: "webhook" :

"webhook": {
  "signature": {
    "header": "X-Webhook-Signature",
    "format": "v1:{signature}",          // how the header carries it
    "algorithm": "HMAC-SHA256", "key": "{DJOMY_CLIENT_SECRET}", "message": "{raw_body}", "encoding": "hex"
  }
}

Paramètres hors du corps — input_schema est le corps de la requête ; ce qui voyage dans le chemin de l’URL, la chaîne de requête ou un en-tête va dans parameters :

"endpoint": { "method": "POST", "url": "https://api.example.com/v1/payouts/{batch_id}/items" },
"parameters": [                          // illustrative provider
  { "name": "batch_id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Payout batch id" },
  { "name": "dry_run", "in": "query", "schema": { "type": "boolean" } },
  { "name": "Idempotency-Key", "in": "header", "required": true, "schema": { "type": "string" } }
]

Condition de succès — quand une réponse 2xx ne suffit pas (par ex. HTTP 200 avec {"code": 1} pour un paiement échoué), le champ de la réponse qui indique que l’appel a réussi :

"success_condition": { "field": "code", "equals": 0 }              // or "in": ["SUCCESS", "PENDING"]

Environnements — dans provider.json, l’URL de base de chaque environnement que le fournisseur documente :

"environments": { "sandbox": { "base_url": "https://sandbox-api.djomy.africa" } }

Dépréciation et retrait — voir § Politique de compatibilité :

"deprecation": {                         // schema.json: required when status is "deprecated", forbidden otherwise
  "reason": "The provider retires this endpoint on 2027-01-31 (changelog link).",
  "replaced_by": "create_charge",        // optional: a capability of the same provider
  "removed_after": "2027-01-31"          // optional: the registry removes the capability after this date
},
"removed_capabilities": [                // provider.json: one tombstone per removed capability
  { "capability": "create_bill", "removed_in": "2027-02-03", "reason": "The provider shut the endpoint down on 2027-01-31.", "alternatives": ["create_charge"] }
]
Règle
Placeholders{ENV_VAR} est un identifiant de connexion déclaré ; {signature} requiert auth.signing (ou est le résultat de webhook.signature) ; {access_token} requiert auth.token_exchange ; {raw_body} est le corps de la requête exactement tel que reçu et {header:Name} la valeur brute d’un en-tête de requête — les deux uniquement dans webhook.signature.message, où un {ENV_VAR} peut aussi être un identifiant qui vous est propre et que canonical_example.ts lit (ex. {header:Response-Timestamp}{SMILEID_PARTNER_ID}sid_request) ; ailleurs, {ENV_VAR} est un identifiant de connexion déclaré. validate rejette un placeholder qui ne se résout en rien
auth.credentialsChaque variable d’environnement une seule fois ; interdit avec auth.type: "none" ; inclut l’auth.env_var de la 1.0. Dès qu’il est présent, chaque lecture de process.env dans canonical_example.ts doit y être déclarée (ou être le secret webhook.signature.key, ou une variable d’environnement signée dans webhook.signature.message) : une variable non déclarée fait échouer la validation, au lieu de l’avertissement de la 1.0
auth.applyin : header, query, body ou path — une clé qui voyage dans le chemin de l’URL, où name est le {placeholder} de endpoint.url qu’elle remplit (ex. /epay/{c}/{ref}/status de Paycard)
auth.token_exchangepath (sur l’URL de base de l’environnement) ou url (un serveur de jetons sur un autre hôte, ex. https://idp.flutterwave.com/…/token) — exactement un des deux. body (paires nom/valeur) avec son content_type (application/x-www-form-urlencoded) pour une requête de jeton encodée en formulaire ; headers ne contient que des en-têtes (in: "header")
webhook.signature.keyExactement un {ENV_VAR} — le secret, jamais une valeur littérale. Il déclare cette variable d’environnement pour la spec
algorithm, encodingHMAC-SHA256 ; hex ou base64 — les seules valeurs utilisées par les specs jusqu’ici ; les autres sont ajoutées avec la première spec qui en a besoin
parametersin : path, query ou header ; name une seule fois par in (les noms d’en-tête ignorent la casse), jamais un nom que auth.apply envoie déjà ; schema est un JSON Schema. Un paramètre path est required: true et remplit un {placeholder} de endpoint.url ; dès que parameters est présent, chaque {placeholder} de l’URL est rempli par un paramètre de chemin ou par auth.apply ({UPPER_SNAKE_CASE} reste une variable d’environnement) ; un paramètre n’est jamais aussi une propriété d’input_schema, et un GET/DELETE avec parameters n’a pas de corps. validate avertit pour une spec 1.1 dont l’URL a des placeholders et pas de parameters. Pas sur un webhook : son URL est la vôtre
success_conditionfield est un chemin pointé (data.status, à travers les éléments de tableau) que response_schema décrit, quand il décrit quelque chose ; exactement un de equals (une chaîne, un nombre, un booléen ou null) ou in (une liste de ces valeurs), du type du champ (0, et non "0", pour un code entier). Seulement ce que la documentation du fournisseur énonce — sinon, une question ouverte. Pas sur un webhook
IdempotenceConvention, signalée par un avertissement de validate : un POST d’un fournisseur de paiement qui déplace de l’argent (paiement, checkout, versement, transfert, débit, remboursement, cash-in/out, confirmation d’OTP — pas une vérification de statut ni une création de bénéficiaire) indique si un nouvel essai est sans danger — son en-tête d’idempotence dans parameters (ou son champ dans input_schema), le comportement dans gotchas, ou une entrée d’open_questions quand la documentation ne dit rien. Un versement réessayé sans cela peut payer deux fois
environmentssandbox et/ou production, chacun avec une base_url de la forme https://host[:port] (sans chemin, requête ni fragment). L’endpoint.url de chaque spec du fournisseur qui n’est pas un webhook doit commencer par l’une d’elles. Un environnement que le fournisseur ne documente pas est omis — jamais deviné — et son absence va dans les open_questions des specs (une convention, non vérifiée)
deprecationreason dit ce que le fournisseur a annoncé, avec sa date quand il en a donné une. replaced_by nomme un autre dossier de capacité du même fournisseur ; removed_after est une date (YYYY-MM-DD)
removed_capabilitiescapability une seule fois par liste, sans dossier restant dans le répertoire du fournisseur ; removed_in est la date du retrait ; reason une chaîne non vide ; alternatives (facultatif) des dossiers de capacité du même fournisseur

Définitions de capability_type

  • synchronous — requête/réponse, résultat disponible immédiatement dans la réponse HTTP
  • asynchronous — le fournisseur traite en arrière-plan ; on obtient le résultat en interrogeant périodiquement un endpoint séparé
  • webhook — le fournisseur envoie un HTTP POST à votre endpoint quand un événement survient
  • sdk — capacité livrée par un script SDK côté client (pas d’endpoint HTTP direct). endpoint.url contient l’URL CDN du script SDK ; endpoint.method vaut null. Le canonical_example.ts exporte un helper de configuration côté serveur ; l’appel réel du SDK est montré dans le bloc de commentaire d’usage. L’identifiant de connexion va dans auth.location: "sdk_param" (l’objet de configuration du SDK).

canonical_example.ts : règles

Règles obligatoires

  1. TypeScript strict — doit compiler avec tsc --noEmit, zéro erreur
  2. fetch natif uniquement — pas d’axios, pas de node-fetch, pas de bibliothèque HTTP externe
  3. Tous les identifiants de connexion via process.env — vérifiés en haut du fichier, avec une erreur levée s’ils manquent
  4. Interfaces TypeScript explicites pour les types d’entrée, de réponse et d’erreur
  5. Exporter la fonction principale
  6. Inclure un exemple d’usage dans un bloc de commentaire en bas
  7. En-tête JSDoc : fournisseur, capacité, version de l’ATSS, capability_type
  8. L’expéditeur d’un message (numéro, sender ID, nom d’expéditeur) vient de process.env.{PROVIDER}_{CHANNEL}_SENDER dans l’exemple d’usage (ex. INFOBIP_WHATSAPP_SENDER), déclaré dans auth.credentials, jamais une valeur littérale : il change entre un expéditeur de test et celui du compte lui-même

Modèle

/**
 * @provider Paycard
 * @capability create_payment
 * @atss 1.0
 * @capability_type synchronous
 */

const PAYCARD_API_KEY = process.env.PAYCARD_API_KEY;
if (!PAYCARD_API_KEY) throw new Error("Missing env: PAYCARD_API_KEY");

interface CreatePaymentInput {
  amount: number;
  currency: string;
  reference: string;
  callback_url: string;
}

interface CreatePaymentResponse {
  id: string;
  payment_url: string;
  status: string;
}

interface PaycardError {
  code: string;
  message: string;
}

export async function createPayment(
  input: CreatePaymentInput
): Promise<CreatePaymentResponse> {
  const response = await fetch("https://mapaycard.com/epay/create/", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ ...input, c: PAYCARD_API_KEY }),
  });

  if (!response.ok) {
    const error: PaycardError = await response.json();
    throw new Error(`Paycard error ${response.status}: ${error.message}`);
  }

  return response.json() as Promise<CreatePaymentResponse>;
}

/*
Usage example:

const payment = await createPayment({
  amount: 100000,
  currency: "GNF",
  reference: "order_123",
  callback_url: "https://myapp.com/callback",
});

// Store payment.operation_reference immediately
// Always verify payment status server-side before fulfilling the order
*/

Ce qui ne doit jamais apparaître dans canonical_example.ts

  • Tout import npm (axios, node-fetch, etc.) — fetch natif uniquement
  • require() — modules ES uniquement
  • Des clés d’API ou des secrets codés en dur
  • Le type any sans commentaire expliquant pourquoi

Cycle de vie du statut

unverified → draft → ready → verified → deprecated → archived

unverified : critères

  • schema.json passe npm run validate (structure + bloc evidence)
  • Bloc evidence présent avec source_url, source_reference, extracted_at, snapshot_hash, et au moins une entrée spans[] ancrée dans la source
  • Généré automatiquement par le pipeline de discovery (core/tools/discovery) — les contributeurs n’écrivent pas à la main de specs unverified
  • Pas encore relu par un humain au-delà des contrôles automatiques — un mainteneur qui relit la PR la promeut en draft (demande plus de travail) ou en ready (semble correcte) en modifiant status et en retirant ou en gardant evidence comme documentation

ready : critères

  • schema.json passe npm run validate
  • canonical_example.ts compile avec tsc --noEmit
  • gotchas[] contient au moins 1 entrée précise et actionnable
  • example_prompt n’est pas vide
  • provider.json existe dans le dossier parent du fournisseur
  • Le champ status est à ready

Une spec ready est visible dans le serveur MCP.

verified : critères

  • Tous les critères ready sont remplis
  • L’un des suivants :
    • npm run test:live -- --provider <slug> réussit (code de sortie 0) — method: "test_live" ; son --report donne le spec_hash de chaque capacité
    • Une intégration qui fonctionne : afrotools/examples, ou une app en usage — method: "app"
    • Un rapport d’intégration qu’un mainteneur a accepté (voir CONTRIBUTING.md § Report an integration) — method: "community_report"
  • Le bloc verification consigne la méthode, l’environnement, la date et spec_hash
  • Un mainteneur a passé le statut à verified

verified dit que la spec a correspondu à l’API une fois, dans verification.environment. Une spec vérifiée en sandbox n’a pas été essayée avec des identifiants de production. test:live cible toujours et uniquement un sandbox — il refuse de s’exécuter contre un fournisseur sans sandbox (trafic de production réel, aucun filet de sécurité automatique) ; voir CONTRIBUTING.md § Live verification.

AI Ready

AI Ready est une promesse de tout le registre, pas un badge par fournisseur : chaque fournisseur publié a des specs ATSS à partir desquelles un agent peut réaliser l’intégration sans avoir à analyser des pages de documentation. Cela ne veut pas dire qu’une spec est sans erreur — une spec peut être fausse tant qu’elle n’est pas testée, ce que le champ status indique à l’agent. Ne jamais décrire AI Ready comme « vérifié », « testé » ou « prêt pour la production ».


Politique de compatibilité

Des agents maintiennent des intégrations générées à partir de ces specs. Un changement qui rend une telle intégration fausse sans prévenir son agent est cassant (breaking) :

  • retirer une capacité : supprimer son dossier, ou repasser une spec servie (unverified, ready, verified, deprecated) à draft ou à archived, que le MCP ne sert pas ;
  • retirer ou renommer un champ de response_schema ;
  • retirer ou renommer un champ d’input_schema ou de parameters, y compris un changement de casse ou un déplacement vers un autre endroit du corps. Un seul déplacement n’est pas cassant : une propriété de premier niveau d’input_schema qui devient un paramètre du même nom, ou l’inverse (la migration ATSS 1.1) — l’agent lit le nouvel emplacement dans la spec. Seuls les noms d’en-tête ignorent la casse.

Changer le type d’un champ, rendre obligatoire une entrée facultative ou restreindre un enum casse aussi les intégrations ; le relecteur en juge. Ajouter une capacité, un champ facultatif, un gotcha ou une question ouverte, ou corriger une description, n’est pas cassant.

scripts/spec-diff.js compare les specs d’une PR à main et liste les capacités retirées et les champs retirés ou renommés (un renommage apparaît comme un retrait), et, à titre d’information, les champs de requête qui ont bougé. La CI échoue quand elle trouve un retrait, sauf si la PR porte le label breaking : un mainteneur le pose une fois le retrait voulu, puis relance le job.

Quand le fournisseur déprécie une capacité (annonce sa fin, ou un remplaçant) :

  1. Passez status à "deprecated" avec un bloc deprecation (spec_version: "1.1") : reason, et replaced_by / removed_after quand ils sont connus. Le MCP continue de servir la spec, avec un avertissement qui nomme replaced_by.
  2. Une fois que le fournisseur l’a arrêtée (ou après removed_after), supprimez le dossier et ajoutez un tombstone (une entrée qui garde la trace de la capacité retirée) à removed_capabilities dans provider.json. get_spec répond alors par une erreur portant removed_in, reason et alternatives, au lieu d’un simple « introuvable ». Cette PR a besoin du label breaking.

Quand le registre retire une capacité que le fournisseur propose toujours (une réécriture, une spec qui n’aurait pas dû être écrite) : supprimez le dossier, avec le label breaking, sans statut deprecated ni tombstone — get_spec répond avec les capacités restantes du fournisseur. Exemple : les 25 capacités Flutterwave que afrotools/afrotools#94 a retirées.


Gotchas : guide d’écriture

Bien :

"Always verify payment status server-side before fulfilling an order.
The callback URL alone is not sufficient — it can be forged."

Mal :

"Read the docs carefully."

Les gotchas doivent être : précis, actionnables, fondés sur une vraie expérience d’intégration.


Index des fournisseurs

Voir le tableau généré automatiquement dans README.md — il est régénéré par la CI (scripts/generate-readme.js) à chaque fusion dans main et n’est jamais désynchronisé. Ce fichier gardait autrefois une seconde copie de ce tableau, tenue à la main ; elle a divergé de la réalité en quelques semaines et ne vaut pas la peine d’être réintroduite ici.