Pay PodRead with @stripe/link-cli
The MPP API reference documents the protocol; this page walks through the eight commands that take you from an empty terminal to a paid narration using @stripe/link-cli and a Stripe Link wallet. No on-chain wallet, no Tempo bridging — just a Link account and a Node.js install.
PodRead's MPP endpoints advertise both tempo and stripe challenges in the same 402 response. link-cli reads the stripe-method challenge, mints a Stripe shared payment token (SPT) you approve in the Link app, and replays the request with the credential.
1. Install link-cli
Install globally so the link-cli binary is on your PATH.
npm i -g @stripe/link-cli
link-cli --version
2. Authenticate
Pair the CLI with your Stripe Link account. The --client-name label is what you'll see when approving requests in the Link app — pick something you'll recognize.
link-cli auth login --client-name podread-cli
A device-pairing flow opens — confirm in the Link app to finish.
3. List payment methods
List the cards/bank accounts attached to your Link wallet and copy the id of the one you'll spend from.
link-cli payment-methods list
4. Probe for the challenge
Hit the endpoint without a credential to read the 402 challenge. The WWW-Authenticate header carries both methods; you want the stripe one's networkId and amount. Fetch it with curl, then feed the header value into link-cli mpp decode --challenge to extract the structured fields.
# Step 1 — capture the WWW-Authenticate header from a 402 response
CHALLENGE=$(curl -s -i -X POST https://podread.app/api/v1/mpp/narrations \
-H "Content-Type: application/json" \
-d '{"text":"Hello, world.","voice":"felix","title":"Hello"}' \
| grep -i '^www-authenticate:' \
| sed 's/^www-authenticate: //i' | tr -d '\r')
# Step 2 — let link-cli pick out the stripe-method fields
link-cli mpp decode --challenge "$CHALLENGE"
Decode prints network_id, the amount in cents (under request_json.amount), and the challenge id/expires. The --challenge flag takes the raw header value — single-quote it or capture it into a shell variable first; embedded commas and equals signs will otherwise confuse the shell.
The stripe-method challenge will carry PodRead's networkId — profile_61UbsYpl4SxclGNZQA6UbsYpSGSQ2OwdqYukbeHlQOwS — and an amount in cents that depends on the requested voice's tier (see Pricing below).
5. Create a spend request
A spend request is the SPT precursor — it tells Stripe "I want to authorize this much from this payment method to this merchant network." The --amount must match the stripe challenge from Step 4 exactly. Standard voices (wren, felix, sloane, archer, gemma, hugo, quinn, theo) are 150 cents; Premium voices (elara, callum, lark, nash) are 250 cents — see Pricing.
link-cli spend-request create \
--credential-type shared_payment_token \
--network-id profile_61UbsYpl4SxclGNZQA6UbsYpSGSQ2OwdqYukbeHlQOwS \
--amount 150 \
--currency usd \
--payment-method-id 'csmrpd_*ABC123' \
--context "PodRead narration synthesis (Standard tier / Felix voice). One-time charge to convert an article into an MP3 podcast episode." \
--line-item 'name:Standard narration (Felix),quantity:1,unit_amount:150' \
--total 'type:total,display_text:Standard narration,amount:150' \
--request-approval
A few flag notes the CLI's --help doesn't make obvious:
--payment-method-id(not--payment-method) — passing the wrong name fails withUnknown flag.--contextis required and must be at least 100 characters. This text is what your Link app shows on the approval card; write it for a human.-
--line-itemand--totaleach take a single string of comma-separatedkey:valuepairs (one flag invocation = one item). Repeating the flag does not set fields on a single item — it creates separate items. Schema:--line-item:name(required),quantity,unit_amount(cents),description,sku,product_url,image_url.--total:type,display_text,amount(cents) — all required.
- The parser splits values on
,as well as keys, so values cannot contain commas. Pick comma-freename/display_textstrings (e.g."Standard narration (Felix)"instead of"Felix, Standard"). --request-approvalpushes the request to your Link app and starts polling. Omit it and the spend request stays increatedforever.
Note the resulting lsrq_… id — you'll pass it back in Step 7.
6. Approve in the Link app
Open the Link app on your phone. You should see a pending approval matching the --client-name you set in Step 2 — the --context text and line items from Step 5 render on the approval card. Tap to approve.
The spend-request create --request-approval call from Step 5 polls in the background and prints the final object once Stripe propagates the approval — usually within a couple of seconds. If you closed that terminal, poll manually:
# Compact polling — prints just the status string each tick
link-cli spend-request retrieve 'lsrq_1TSkEkHPzSsifbC1VtmAFGyD' \
--interval 2 --max-attempts 150 \
--format json --filter-output status
--filter-output status --format json reduces each poll tick to a single ["pending_approval"] / ["approved"] line instead of re-dumping the whole object — useful when screen-recording the flow. Drop those flags if you want to see the full approval card payload.
Once status is approved, the SPT is mintable for that spend request. link-cli mpp pay in the next step does the actual minting + redemption against PodRead.
7. Pay the endpoint
link-cli mpp pay mints an SPT against the approved spend request, builds the credential (base64url JSON of {challenge, payload: {spt}}), and replays the request with Authorization: Payment …. On success PodRead returns 201 with a nar_… id and a Payment-Receipt header.
Real narration payloads are multi-KB article text with paragraphs, quotes, and apostrophes — well past anything you want to inline as a single shell-quoted string. --data takes a string only (no @file magic), so write the JSON to a file first and feed it in via shell command-substitution:
# 1. Build the request body in a file (any editor; here, a heredoc)
cat > narration.json <<'JSON'
{
"text": "The first principle is that you must not fool yourself, and you are the easiest person to fool. Richard Feynman, 1974, said that to a graduating class at Caltech. He was talking about cargo-cult science, but the line generalizes: it is the same warning every careful operator has had to issue to themselves at some point. Verify, then trust. Reproduce, then publish. The world owes us nothing in the way of confirmation; we have to go and check.",
"voice": "felix",
"title": "Cargo cult"
}
JSON
# 2. Pay — note the $(cat …) substitution and the single quotes around the lsrq id
link-cli mpp pay https://podread.app/api/v1/mpp/narrations \
--spend-request-id 'lsrq_1TSkEkHPzSsifbC1VtmAFGyD' \
--method POST \
--data "$(cat narration.json)" \
--format json
Use "$(cat …)", not '$(cat …)' — single quotes in shell suppress substitution. The double-quoted form preserves the JSON exactly while letting $(…) expand. The response body will look like:
{ "id": "nar_aB3xK9mQ2pL7" }
8. Poll for audio
Narrations synthesize asynchronously. Poll the show endpoint until audio_url appears (typically 10–30 seconds for Standard, longer for Premium / long inputs).
curl https://podread.app/api/v1/mpp/narrations/nar_aB3xK9mQ2pL7
Status progresses through pending → preparing → processing → complete. Once complete the response includes a signed audio_url good for 5 minutes; download the mp3 before it expires. Anonymous narrations are garbage-collected 24 hours after creation.
Pricing
Twelve voices across two tiers. Standard voices use Google's Standard synthesis ($1.50 SPT); Premium voices use Chirp3-HD ($2.50 SPT). Pass the lower-case key (e.g. felix) as voice in the request body — the tier is inferred server-side and dictates the challenge amount.
Voice catalog
| Voice | Accent | Gender | Tier | Stripe SPT (USD) |
|---|---|---|---|---|
| wren | British | Female | Standard | $1.50 |
| felix | British | Male | Standard | $1.50 |
| sloane | American | Female | Standard | $1.50 |
| archer | American | Male | Standard | $1.50 |
| gemma | British | Female | Standard | $1.50 |
| hugo | British | Male | Standard | $1.50 |
| quinn | American | Female | Standard | $1.50 |
| theo | American | Male | Standard | $1.50 |
| elara | British | Female | Premium | $2.50 |
| callum | British | Male | Premium | $2.50 |
| lark | American | Female | Premium | $2.50 |
| nash | American | Male | Premium | $2.50 |
-
- Voice
- wren
- Accent
- British
- Gender
- Female
- Tier
- Standard
- Stripe SPT (USD)
- $1.50
-
- Voice
- felix
- Accent
- British
- Gender
- Male
- Tier
- Standard
- Stripe SPT (USD)
- $1.50
-
- Voice
- sloane
- Accent
- American
- Gender
- Female
- Tier
- Standard
- Stripe SPT (USD)
- $1.50
-
- Voice
- archer
- Accent
- American
- Gender
- Male
- Tier
- Standard
- Stripe SPT (USD)
- $1.50
-
- Voice
- gemma
- Accent
- British
- Gender
- Female
- Tier
- Standard
- Stripe SPT (USD)
- $1.50
-
- Voice
- hugo
- Accent
- British
- Gender
- Male
- Tier
- Standard
- Stripe SPT (USD)
- $1.50
-
- Voice
- quinn
- Accent
- American
- Gender
- Female
- Tier
- Standard
- Stripe SPT (USD)
- $1.50
-
- Voice
- theo
- Accent
- American
- Gender
- Male
- Tier
- Standard
- Stripe SPT (USD)
- $1.50
-
- Voice
- elara
- Accent
- British
- Gender
- Female
- Tier
- Premium
- Stripe SPT (USD)
- $2.50
-
- Voice
- callum
- Accent
- British
- Gender
- Male
- Tier
- Premium
- Stripe SPT (USD)
- $2.50
-
- Voice
- lark
- Accent
- American
- Gender
- Female
- Tier
- Premium
- Stripe SPT (USD)
- $2.50
-
- Voice
- nash
- Accent
- American
- Gender
- Male
- Tier
- Premium
- Stripe SPT (USD)
- $2.50
Tier summary
SPT prices include Stripe's per-redemption fee (2.9% + $0.30) — that's why the SPT row sits a bit above the on-chain Tempo row. The --amount you pass to spend-request create must match the stripe-method challenge from Step 4 exactly; a mismatch will fail HMAC verification on retry.
| Tier | Voices | Tempo (USD) | Stripe SPT (USD) |
|---|---|---|---|
| Standard | wren, felix, sloane, archer, gemma, hugo, quinn, theo | $0.75 | $1.50 |
| Premium | elara, callum, lark, nash | $2.00 | $2.50 |
-
- Tier
- Standard
- Voices
- wren, felix, sloane, archer, gemma, hugo, quinn, theo
- Tempo (USD)
- $0.75
- Stripe SPT (USD)
- $1.50
-
- Tier
- Premium
- Voices
- elara, callum, lark, nash
- Tempo (USD)
- $2.00
- Stripe SPT (USD)
- $2.50
See the MPP API reference Pricing section for the full per-scheme breakdown.