Signed receipts for client-declared API exchanges.
# Datoka API — démarrage rapide
Production : https://datoka-api.bhazarstudio.workers.dev
Prix d’émission : 0,002 USDC sur Base. Préparation et vérification gratuites.
Les reçus attestent une déclaration du client, pas une observation indépendante.
## Installation locale (Node.js 24+)
Télécharger le SDK versionné et conserver ces fichiers dans votre projet :
```sh
curl --fail https://datoka-api.bhazarstudio.workers.dev/sdk/0.3.11/datoka-api.js -o datoka-api.mjs
curl --fail https://datoka-api.bhazarstudio.workers.dev/sdk/0.3.11/manifest.json -o manifest.json
curl --fail https://datoka-api.bhazarstudio.workers.dev/sdk/0.3.11/check-integration.mjs -o check-integration.mjs
node check-integration.mjs ./datoka-api.mjs
```
Sous Windows PowerShell, utiliser `curl.exe`. Le test compare l'empreinte SHA-256
du SDK au manifeste de cette version avant de l'importer. Il vérifie une capture
locale, la préparation gratuite, la découverte MCP et les offres HTTP/MCP.
Il ne signe rien, ne paie rien et n'émet aucun reçu ; il ne teste donc pas
la signature d'un nouveau reçu. Aucun fichier privé ni dépendance npm requis.
Le manifeste et `/sdk/0.3.11/SHA256SUMS` permettent de conserver l'empreinte
acceptée. Cette empreinte publiée sur le même site détecte les différences de
fichier ; elle ne constitue pas une preuve d'origine indépendante du site.
Le SDK est un module ES autonome ; aucun paquet npm « datoka-api » n'est publié.
L'URL versionnée reste fixe ; `/sdk/datoka-api.js` et `/sdk/manifest.json`
suivent la dernière version. Ne pas importer une URL distante à chaque exécution.
## Capturer un échange sans payer
```js
import { captureExchange } from './datoka-api.mjs';
const captured = await captureExchange('https://votre-api.example/data', {
method: 'GET',
signal: AbortSignal.timeout(30_000),
}, { apiId: 'mon-api', maxBodyBytes: 1024 * 1024 });
const data = await captured.response.text();
if (captured.event) {
// Sauvegarder cet événement pour demander un reçu ultérieurement.
console.log(captured.event);
} else {
// L’appel métier a eu lieu. Ne pas le relancer pour une erreur de capture.
console.error(captured.captureError);
}
```
La capture appelle seulement votre API. Aucun paiement ni appel Datoka n’est
fait. Les corps sont hachés localement ; aucun en-tête n’est capturé par défaut.
La réponse reste lisible. Limite par corps : 1 Mio par défaut, 16 Mio maximum.
Une requête trop volumineuse est refusée avant envoi. Une réponse trop volumineuse
reste disponible mais sans événement. Les redirections sont refusées. Les flux
continus ne conviennent pas : fournir un délai d’expiration. Les octets sont
ceux de Fetch après décodage HTTP, pas les octets réseau ou TLS.
## Préparer gratuitement sans clé API
```js
import { ApiClient } from './datoka-api.mjs';
const api = new ApiClient('https://datoka-api.bhazarstudio.workers.dev');
const prepared = await api.prepare(captured.event);
const publicKeys = await api.publicKeys();
```
`ApiClient` n'envoie aucun paiement automatiquement. Les clés récupérées servent
à la découverte ; pour vérifier un reçu, établir séparément leur confiance.
Un jeton peut être fourni comme deuxième argument pour le pilote privé.
## Acheter un reçu
Parcours autonome complet, sans fichier interne : /first-receipt.
Exemples d’utilisation et portée des reçus : /use-cases.
Utiliser PaidApiClient avec un signataire EOA compatible viem, financé en USDC
natif sur Base. Conserver la clé privée dans votre environnement sécurisé.
```js
import { PaidApiClient } from './datoka-api.mjs';
const client = new PaidApiClient({
origin: 'https://datoka-api.bhazarstudio.workers.dev',
network: 'eip155:8453',
recipient: '0x4a7A972F84E2A64b3B9B64AC1383Df5c413c0E59',
maximumAtomic: 2000n,
});
// signer : signataire de votre portefeuille ; captured.event : événement ci-dessus.
const pending = await client.prepare({
event: captured.event,
idempotencyKey: crypto.randomUUID(),
}, signer);
// OBLIGATOIRE : sauvegarder pending durablement AVANT la ligne suivante.
// Exemple complet avec fsync et verrou : /first-receipt et examples/first-receipt.ts.
const paid = await client.resume(pending);
// Sauvegarder paid, vérifier sa signature avec une clé publique de confiance.
```
prepare signe une autorisation mais ne la soumet pas. resume peut effectuer le
paiement. Après un délai dépassé ou un paiement en attente, réutiliser le même
pending ; ne jamais recréer une autorisation automatiquement. Le budget est par
achat : l’application doit aussi imposer son plafond global.
## MCP et HTTP
Point d’accès MCP Streamable HTTP :
https://datoka-api.bhazarstudio.workers.dev/mcp
Configuration pour les clients acceptant mcpServers/url :
```json
{"mcpServers":{"datoka-api":{"url":"https://datoka-api.bhazarstudio.workers.dev/mcp"}}}
```
Un client MCP ordinaire peut préparer et vérifier. L’émission exige un client
compatible x402 et un portefeuille ; connecter le serveur ne suffit pas à payer.
Outils : prepare_api_exchange, issue_api_receipt, verify_api_receipt,
get_api_public_keys. Pour payer : params._meta["x402/payment"].
Contrats HTTP complets : /openapi.json. Exemple synthétique : /example.json.
## Vérification et confiance
Vérifier la signature avec une clé d’émetteur obtenue par un canal de confiance,
puis comparer les empreintes aux données originales avec verifyExchangePayloads.
Une simple correspondance d’empreintes ne vérifie pas la signature.
Conserver les données originales et le reçu chez vous. Ne pas transmettre de
contenus ou de secrets au service. Une empreinte n’est pas un chiffrement.
## Vérificateur dans le navigateur
Ouvrir /verify/ : sélectionner le reçu (ou le résultat payé complet), une clé
publique de confiance obtenue séparément, puis éventuellement le corps de réponse
original. Le contrôle se fait localement, sans paiement ni envoi des fichiers.
Le corps de réponse est comparé octet par octet par son empreinte ; les autres
contenus ne sont pas comparés par cette interface. Le SDK permet la vérification
complète des contenus. Un navigateur compatible Web Crypto Ed25519 est requis.
## Dépannage HTTP et MCP
Depuis 0.3.11, PaidApiClient expose PaymentClientError : code fixe, action,
retryable, retryAfterMs et paymentMayHaveSettled. Après une interruption réseau
ou payment_pending, action vaut resume_same_purchase : réutiliser le pending
sauvegardé, avec un nombre de reprises et un délai total bornés par votre application.
Le SDK ne relance rien automatiquement. Un refus définitif ou une réponse
incohérente demande inspect_saved_purchase ; ne pas créer un nouvel achat pour
le contourner. Les messages bruts du fournisseur ne sont pas exposés.
```js
import { PaymentClientError } from './datoka-api.mjs';
try {
const paid = await client.resume(pending);
// Sauvegarder et vérifier le reçu avant de l'utiliser.
} catch (error) {
if (error instanceof PaymentClientError) {
console.error({ code: error.code, action: error.action,
retryAfterMs: error.retryAfterMs, paymentMayHaveSettled: error.paymentMayHaveSettled });
}
// Conserver pending. Aucune nouvelle signature automatique.
throw error;
}
```
Pour transmettre le reçu à un autre agent et le vérifier gratuitement hors
ligne : createEvidenceBundle / verifyEvidenceBundle et exemple evidence.mjs
dans /use-cases. Le manifeste du SDK contient les empreintes du SDK et de cet exemple.
Les opérations HTTP et le point MCP acceptent POST avec `Content-Type: application/json`.
Pour MCP, envoyer également `Accept: application/json, text/event-stream`.
Accept: application/json is supported. A 406 means JSON is excluded or not accepted.
Les refus 405 comportent `Allow` ; les erreurs HTTP comportent un lien `rel="help"`.
GET et DELETE sur ce serveur MCP sans session peuvent répondre 405 normalement.
Une réponse 402 proposant un prix ne signifie pas qu’un paiement a eu lieu.