Overview
https://api.unlimitedtts.com is a paid text-to-speech API for AI agents and automated systems. It offers 3 independent payment flows, all priced from the same per-character rate: pay each request directly in USDC over x402 with no account, prepay a USDC balance and authorize each request with a signature, or fund prepaid card credits through Stripe Checkout.
This page is generated from the API's own contract. Read the canonical OpenAPI document, the agent guide, and the voice list at integration time — they are authoritative, and a client should read them rather than hardcode anything below.
Choose a payment flow
| Flow | Endpoints | Identity | Minimum |
|---|---|---|---|
| Card credits | GET /ttsPOST /ttsGET/POST /top-upGET /credits/:accountId |
x-credit-account bearer token | 500 cents per top-up; request debits can be sub-cent. |
| Direct x402 | GET /x402/ttsPOST /x402/tts |
No API account or prepaid balance; the payment credential authorizes the request. | No application minimum and no top-up. The only lower bound is one atomic USDC unit (0.000001 USDC). |
| Prepaid x402 | POST /x402/depositGET /x402/accounts/:addressGET /x402/ttsPOST /x402/tts |
The payer's public EVM address. This is not a bearer token: spending requires an EIP-712 signature from that address on every request. | — |
Pick one flow per request and do not mix them. /x402/tts never
accepts x-credit-account, and /tts never reads x402 payment headers.
Stripe funds card credits only; it is not an x402 top-up.
- Autonomous wallet agent, occasional requests: direct x402. No account, no balance, no setup — but every request pays for on-chain settlement latency.
- Autonomous wallet agent, sustained traffic: prepaid x402. Deposit once, then each request costs a local signature check instead of a facilitator round trip.
- A human or app with a card: card credits. This is the only flow with asynchronous jobs and stored audio.
x402 Quick Start
- Read
GET https://api.unlimitedtts.com/openapi.jsonfor the current contract. - Verify
GET /.well-known/x402-service-metadataagainst a public key you pinned out of band. Never bootstrap trust from the challenge itself. - Choose a model from
GET /tts/modelsand a voice fromGET /tts/voices. - Send your JSON body to
POST /x402/ttswith no payment header. - On
402, Base64-decodePAYMENT-REQUIREDand select a compatible entry fromaccepts. - Pay with
@x402/fetch, or sign the x402 v2 payload yourself. - Retry the identical method, URL, query, and body with
PAYMENT-SIGNATURE. - Accept
200 audio/mpegand keepPAYMENT-RESPONSEas the settlement receipt.
Network and settlement
Production settles the exact EVM scheme in
USDC (six-decimal atomic units) on Base mainnet
(eip155:8453) through
https://facilitator.payai.network. Treat the challenge as the authority for
network, asset, recipient, amount, and timeout; never hardcode the receiving address.
Payment Identifier and replay
Every paid PaymentPayload must carry the official x402 Payment Identifier
extension. It is the idempotency key: persist the identifier alongside the exact request, the
audio, and the receipt. If a response is lost in transit, replaying the identical paid request
within 24 hours returns the original audio and receipt with no second
charge and no second synthesis.
PAYMENT_OUTCOME_UNKNOWN is the one exception: it is not
retryable. An agent must not construct and pay a replacement request without explicit human
approval.
Raw x402 v2 payload
A manual client Base64-encodes this JSON into PAYMENT-SIGNATURE:
{
"x402Version": 2,
"accepted": {
"...": "the complete selected PAYMENT-REQUIRED.accepts entry, copied unmodified"
},
"payload": {
"signature": "0x...",
"authorization": {
"...": "scheme-specific exact-EVM authorization"
}
},
"extensions": {
"payment-identifier": { "id": "your-stable-16-to-128-char-id" }
}
}
accepted and the Payment Identifier extension are both required. Top-level
scheme and network fields are the legacy v1 shape and are not
substitutes for accepted. A malformed or legacy envelope returns
400 INVALID_PAYMENT_PAYLOAD before any verification or synthesis.
Quoting without disclosing text
The direct flow sends your text in the unpaid request. If an agent must not disclose text
before payment, quote by hash instead: POST /x402/tts/quote takes only the character
length and a SHA-256 digest, and returns a signed quoteToken. Send
{quoteToken} alone to get the challenge, then {quoteToken, text} with
the payment.
A quote is a price, not a ticket: read expiresAt from the response rather than
assuming a fixed lifetime, and note that within that lifetime the same token can be spent more
than once — each spend is charged. Payment is what is consumed, not the token. The
length-and-digest binding is enforced at synthesis, where a mismatch returns
400 QUOTE_BODY_MISMATCH and charges nothing.
Prepaid x402
Deposit once with POST /x402/deposit using the ordinary x402 challenge and paid
retry. The balance is credited to the paying EVM address after the transfer reaches the required
confirmation depth on Base mainnet. Afterwards, /x402/tts requests skip the
facilitator entirely.
The balance is not a bearer token. Spending requires an EIP-712
PrepaidRequest signature from the owning address on every request,
binding the method, path, body hash, maximum amount, nonce, and expiry. EOA signers only —
EIP-1271 smart-account signatures are rejected, matching the exact scheme's EIP-3009
requirement.
Card Quick Start
- Choose a model and voice from
GET /tts/modelsandGET /tts/voices. - Send a request to
POST /tts, or theGET /ttsfallback. - With no funded balance the API returns
402carrying atopUpUrl. - Open
topUpUrl, complete Stripe Checkout, and save the returnedx-credit-accountvalue. - Retry
/ttswith thex-credit-accountheader. Only the exact synthesis price is debited. - For async jobs, poll
/tts/jobs/:jobIdand download from the returned link. - Check the remaining balance at
GET /credits/:accountId.
An individual debit can be far below one cent; the $5 minimum applies only to a funding transaction. Failed card-credit requests restore the exact debit to the balance. On Stripe Checkout, confirm the country selector matches the card's billing country — proxy geolocation often picks the wrong default.
Models & Voices
| Model id | Name | Max characters | Price per character | Available |
|---|---|---|---|---|
openai/tts-1 |
OpenAI TTS-1 | 4096 | $0.000018 | Yes |
openai/tts-1-hd |
OpenAI TTS-1 HD | 4096 | $0.000036 | Yes |
Voice ids
openai_alloy, openai_ash, openai_coral, openai_echo, openai_fable, openai_onyx, openai_nova, openai_sage, openai_shimmer
The default voice for openai/tts-1 is
openai_alloy. Read
GET /tts/voices?model=… for the current list per model.
Endpoints
Service
Service metadata.
| Method | Path | Summary |
|---|---|---|
| GET | / | Get service health and endpoint links. |
Documentation
Machine-readable documentation endpoints.
| Method | Path | Summary |
|---|---|---|
| GET | /docs | Get the legacy machine-readable payment and agent guide. |
| GET | /.well-known/x402-service-metadata | Get signed x402 network, asset, payee, pricing, and rotation metadata. |
| GET | /openapi.json | Get the canonical OpenAPI 3.0.3 contract. |
TTS
Text-to-speech operations.
| Method | Path | Summary |
|---|---|---|
| GET | /tts/voices | List supported public voice ids and current pricing. |
| GET | /tts/models | List selectable TTS models, limits, voices, and current pricing. |
| POST | /tts | Synthesize speech using prepaid card credits. |
| GET | /tts | Synthesize speech using a GET fallback and prepaid card credits. |
| POST | /x402/tts/quote | Create a hash-bound x402 TTS quote without disclosing text. |
| POST | /x402/tts | Synthesize speech using an exact per-request x402 USDC payment. |
| GET | /x402/tts | Synthesize speech using a GET fallback and x402 USDC payment. |
TTS jobs
Asynchronous TTS job polling and audio retrieval.
| Method | Path | Summary |
|---|---|---|
| GET | /tts/jobs/{jobId} | Get an asynchronous TTS job. |
| GET | /tts/jobs/{jobId}/audio | Download audio for a completed asynchronous TTS job. |
Card credits
Prepaid Stripe-funded credit accounts.
| Method | Path | Summary |
|---|---|---|
| GET | /top-up | Open the Stripe Checkout top-up launcher. |
| POST | /top-up | Create a Stripe Checkout Session. |
| GET | /top-up/complete | Verify a returned Stripe Checkout Session and credit the account. |
| GET | /top-up/status | Check whether a credit account is active after Checkout. |
| GET | /credits/{accountId} | Get the balance of a prepaid credit account. |
x402 prepaid
Signature-authenticated prepaid USDC balances that remove facilitator and settlement latency from each request.
| Method | Path | Summary |
|---|---|---|
| POST | /x402/deposit | Fund a prepaid x402 balance with one confirmed USDC payment. |
| GET | /x402/accounts/{address} | Get a prepaid x402 balance. |
Examples
Voice ids always use the provider_voice form; bare upstream names like
nova are rejected.
x402: initial POST challenge
POST https://api.unlimitedtts.com/x402/tts
Content-Type: application/json
{
"text": "Hello from an AI agent",
"model": "openai/tts-1",
"voice": "openai_alloy",
"speed": 1
}
x402: paid POST retry — same method, same URL
POST https://api.unlimitedtts.com/x402/tts
Content-Type: application/json
PAYMENT-SIGNATURE: <base64-x402-v2-payment-payload>
{
"text": "Hello from an AI agent",
"model": "openai/tts-1",
"voice": "openai_alloy",
"speed": 1
}
x402: GET challenge, then the paid GET retry
GET https://api.unlimitedtts.com/x402/tts?text=Hello%20world&voice=openai_alloy
GET https://api.unlimitedtts.com/x402/tts?text=Hello%20world&voice=openai_alloy
PAYMENT-SIGNATURE: <base64-x402-v2-payment-payload>
x402: quote by hash without disclosing text
POST https://api.unlimitedtts.com/x402/tts/quote
Content-Type: application/json
{
"textLength": 11,
"textSha256": "<lowercase-sha256-hex-of-trimmed-utf8-text>",
"model": "openai/tts-1",
"voice": "openai_alloy"
}
Prepaid x402: signature-authenticated request
POST https://api.unlimitedtts.com/x402/tts
Content-Type: application/json
x-x402-account: <your-evm-address>
x-x402-nonce: <unique-per-request>
x-x402-expiry: <unix-seconds>
x-x402-max-amount: <micro-usd-ceiling>
x-x402-signature: <eip-712-signature>
{
"text": "Hello world",
"voice": "openai_alloy"
}
Card credits: synchronous POST
POST https://api.unlimitedtts.com/tts
Content-Type: application/json
x-credit-account: <your-account-uuid>
{
"text": "Hello world",
"model": "openai/tts-1",
"voice": "openai_alloy",
"speed": 1
}
Card credits: async job with no audio retention
POST https://api.unlimitedtts.com/tts?async=true&storeAudio=false
Content-Type: application/json
x-credit-account: <your-account-uuid>
{
"text": "Hello world",
"voice": "openai_alloy"
}
Pricing & Limits
- Pricing basis: input character count × selected model rate × shared markup, with a 1.2× markup, rounded up to the nearest micro-dollar. One micro-dollar is one atomic USDC unit; card requests debit the same micro-USD amount (10000 micro-USD per cent).
- openai/tts-1: $0.000018 per input character, up to 4096 characters.
- GET requests: up to 1500 characters.
- Card credits: $5 minimum funding transaction; only
the exact request price is debited. Async at 20000 characters or with
?async=true;idempotency-keyup to 512 characters. - x402: No application minimum and no top-up. The only lower bound is one atomic USDC unit (0.000001 USDC).
- Rate limit: 30 requests per 60 seconds. Scope: GET/POST /tts, paid GET/POST /x402/tts retries, and GET /tts/jobs/:jobId/audio. The initial unpaid x402 challenge does not consume a slot, and verified prepaid x402 requests are excluded.
- Rate-limit headers:
Retry-After,X-RateLimit-Limit,X-RateLimit-Policy,X-RateLimit-Remaining,X-RateLimit-Reset,X-RateLimit-Scope.
These values change. Read GET /openapi.json and GET /tts/models
rather than hardcoding them in a client.
Error codes
Errors return a structured body with a stable code. The full set:
CONFIG_ERROR, INVALID_JSON, INVALID_REQUEST, UNKNOWN_TTS_MODEL, INVALID_VOICE, INVALID_SPEED, TEXT_TOO_LONG, TTS_PROVIDER_NOT_CONFIGURED, INVALID_PAYMENT_PAYLOAD, INVALID_QUOTE, QUOTE_EXPIRED, QUOTE_BODY_MISMATCH, UNSUPPORTED_CONTENT_TYPE, PAYMENT_REQUIRED, PAYMENT_ERROR, PAYMENT_IDENTIFIER_CONFLICT, PAYMENT_REPLAY_PENDING, PAYMENT_OUTCOME_UNKNOWN, REPLAY_CACHE_UNAVAILABLE, RATE_LIMITED, NOT_FOUND, UNAUTHORIZED, JOB_NOT_READY, AUDIO_NOT_STORED, TTS_UPSTREAM_ERROR, TTS_UNAVAILABLE, INTERNAL_ERROR
Privacy & Storage
- Direct x402 and prepaid x402 responses are synchronous and never enter the async storage workflow.
- The application does not log or retain raw unpaid-challenge text, and does not forward it to the synthesis provider before payment verifies.
- To avoid sending text before payment at all, use the hash-bound quote flow.
- Card-credit async audio is retained by default so it can be downloaded later. Send
storeAudio=falseto opt out; a completed no-store job has no downloadable link. - Failed card-credit requests restore the exact debit. Crypto charges are final — there is no refund endpoint, and exact idempotent replay within 24 hours is the recovery mechanism.
- Stripe Checkout handles raw card details; this API never receives them.
FAQ
Can an AI agent pay without creating an account?
Yes. The direct x402 flow pays each /x402/tts request in USDC with no API account, bearer token, checkout, prepaid balance, or top-up.
What network and token does x402 use?
Production uses USDC on Base mainnet (eip155:8453) with the x402 v2 exact EVM scheme. Always treat the PAYMENT-REQUIRED challenge as authoritative for the network, asset, recipient, amount, and timeout, and verify the signed service metadata against a pinned key before trusting a payee.
Does x402 require a Coinbase account?
No. The API requires no Coinbase, CDP, or facilitator account. A buyer needs a compatible Base wallet holding USDC and an x402 v2 exact-EVM signer.
Can I use x402 without @x402/fetch?
Yes. @x402/fetch is an optional JavaScript convenience. Any client can implement the raw HTTP flow: read PAYMENT-REQUIRED, sign the selected challenge entry, and retry with a valid x402 v2 PaymentPayload carrying the required Payment Identifier extension in PAYMENT-SIGNATURE.
Does a paid GET retry become a POST?
No. Retry the identical method and URL. A GET challenge is paid with a second GET carrying PAYMENT-SIGNATURE; it never changes to POST.
What happens if the response is lost after I paid?
Replay the identical paid request with the same Payment Identifier within 24 hours. The API returns the original audio and settlement receipt without verifying, synthesizing, or charging again. Do not automatically retry PAYMENT_OUTCOME_UNKNOWN — that one requires explicit approval.
How does pricing work?
Every flow uses the same USD per-character rate for the selected model, rounded up to the nearest micro-dollar. openai/tts-1 is $0.000018 per character. The $5 minimum applies only to a card funding transaction, never to x402 or to an individual synthesis.
Is my text or audio stored?
Direct and prepaid x402 responses are synchronous and do not enter the async storage workflow. Card-credit async audio is retained temporarily by default; storeAudio=false disables that retention.