kino402 · text to speech · USDC on Base or Solana

say it

Text in. MP3 out.

One synchronous x402 call. Send text and an optional ElevenLabs voice id, read the exact quote, pay, and save the audio response. The catalog's 500-character request costs exactly $0.12.

Run the buyerVideo catalogAgent skill

A worked price

500 characters cost $0.12. The published rate is $0.00024 per character; every quote rounds up to the service's $0.001 grid. The unpaid 402 remains authoritative.

const text = ('The number in the 402 is the number that settles. ').repeat(11).slice(0, 500);

Default voice: 21m00Tcm4TlvDq8ikWAM. Supply voice only when you already know an ElevenLabs voice id. Maximum 5000 characters.

A complete, capped buyer

This Node recipe reads the free quote, rejects anything above $0.12, lets x402-fetch build the payment from the returned envelope, retries the identical request, checks the response, and saves kino-tts.mp3 without overwriting an existing file.

// npm install x402-fetch@1.2.0 viem@2.56.0
import { writeFile } from 'node:fs/promises';
import { privateKeyToAccount } from 'viem/accounts';
import { wrapFetchWithPayment } from 'x402-fetch';

const URL = 'https://kino402.com/v1/audio/tts';
const TEXT = ('The number in the 402 is the number that settles. ').repeat(11).slice(0, 500);
const BODY = JSON.stringify({ text: TEXT });
const MAX_SPEND_ATOMIC = 120000n; // $0.12 USDC maximum
const request = { method: 'POST', headers: { 'content-type': 'application/json' }, body: BODY };

// Read the quote for this exact body before signing anything.
const quoteResponse = await fetch(URL, request);
if (quoteResponse.status !== 402) throw new Error(`expected quote, got ${quoteResponse.status}`);
const quote = await quoteResponse.json();
const amount = BigInt(quote.accepts?.[0]?.maxAmountRequired ?? '-1');
if (amount < 0n || amount > MAX_SPEND_ATOMIC) throw new Error(`quote exceeds max spend: ${amount}`);

const account = privateKeyToAccount(process.env.BUYER_PRIVATE_KEY);
const selectBase = requirements => requirements.find(r =>
  r.network === 'base' || r.network === 'eip155:8453');
const paidFetch = wrapFetchWithPayment(fetch, account, MAX_SPEND_ATOMIC, selectBase);
const audioResponse = await paidFetch(URL, request); // handles the 402 retry and payment header
if (!audioResponse.ok) throw new Error(`TTS failed: ${audioResponse.status} ${await audioResponse.text()}`);
if (!(audioResponse.headers.get('content-type') || '').startsWith('audio/mpeg')) throw new Error('expected audio/mpeg');
await writeFile('kino-tts.mp3', Buffer.from(await audioResponse.arrayBuffer()), { flag: 'wx' });

Save the code as buyer.mjs, run npm install x402-fetch@1.2.0 viem@2.56.0, then run BUYER_PRIVATE_KEY=0x... node buyer.mjs. The key must control a Base wallet funded with Base USDC; this recipe selects the Base rail.