Skip to main content

Create a Job from a URL

Use this request to pay for one transcription of an audio or video file that has a public URL. You request a quote, pay it from your wallet with an x402 client, and the service starts the job. To send a local file instead, see Upload a File.

Endpoint​

POST https://api.speechischeap.com/v2/x402/jobs/

Authentication​

There's no API key. The payment authorizes the job. You pay in native USDC on Base with the x402 exact scheme (see Network and Asset). A standard x402 client does these steps for you:

  1. Send the request without payment. The response is 402 Payment Required with a base64-encoded PAYMENT-REQUIRED header and the same quote as JSON. A quote is free and valid for 300 seconds.
  2. Sign the quoted requirement with your wallet, within the price limit you set.
  3. Send the same request again with the signed proof in the PAYMENT-SIGNATURE header. The proof keeps the quote's resource.url, which carries the purchase_id the service issued. The authorization's validBefore must be 6 to 360 seconds after the service's clock when the request arrives. Standard clients set it 300 seconds ahead.
  4. The service settles the payment on Base and answers 202 Accepted with a PAYMENT-RESPONSE header. Confirming the payment takes a few seconds, so the first answer usually has no job id. Send the same request and proof again until it has one. A replay never charges you again. See Recover a Pending Purchase.

The decoded quote for the example request below has this shape. Always pay the payTo address from the live response:

{
"x402Version": 2,
"resource": {
"url": "https://api.speechischeap.com/v2/x402/jobs/?purchase_id=00000000-1111-4222-8333-444444444444",
"description": "One asynchronous Speech is Cheap transcription",
"mimeType": "application/json"
},
"accepts": [{
"scheme": "exact",
"network": "eip155:8453",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "10000",
"payTo": "<PAY_TO_ADDRESS>",
"maxTimeoutSeconds": 300,
"extra": {
"name": "USD Coin",
"version": "2",
"assetTransferMethod": "eip3009"
}
}]
}

amount is in USDC atomic units, so 10000 is $0.01. See How the Price Is Computed.

Request​

Send a JSON body with Content-Type: application/json. The body can be at most 16 KB.

Parameters​

ParameterTypeDefaultDescription
input_urlstring-The URL of the audio or video file to transcribe (required). It must be an HTTP or HTTPS URL without credentials, at most 2,048 characters, and publicly reachable.
input_durationnumber-The length of the file in seconds, from 6 to 86,400 (required). You supply it, and the service prices it before it fetches the file. A longer file fails and is refunded. See How the Price Is Computed.
input_typestringurlurl for this request. Use upload to upload a file.
can_label_audiobooleanfalseWhen enabled, includes an audio classification label in the transcription. See Output and Add-ons for its output and pricing for its price.
can_parse_speakersbooleanfalseWhen enabled, adds speaker_id to each segment based on the speaker's voice. See Output and Add-ons for its output and pricing for its price.
can_parse_wordsbooleanfalseWhen enabled, includes a timecode for every word in the transcription. See Output and Add-ons for its output and pricing for its price.
can_stream_outputbooleanfalseMust be false. x402 jobs are asynchronous, so true fails validation before you pay.
is_privatebooleanfalseWhen enabled, redacts the original input_url and deletes the segments 12 hours after the job completes. See Private Jobs.
is_split_channelbooleanfalseWhen enabled, parses speakers from multi-channel audio by assigning one speaker per channel. Useful for multi-track recordings. See Split Channels.
languagestring""A code from Supported Languages, such as en, in lowercase and at most 16 characters. If omitted, the language is detected for each speech segment. An unsupported code fails the job after payment, and the service refunds it in full.
minimum_confidencenumber0.5Filter out segments that fall below this confidence threshold, from 0 to 1. Send a JSON number, not a string. See Minimum Confidence.
output_formatstring""Format of the transcript sent to your webhook: srt, vtt, or webvtt (an alias for vtt). See SRT and VTT Output.
segment_durationinteger30The maximum duration of a transcription segment in seconds. Send a positive integer. The service adjusts values outside 6 to 30 to that range.
user_agentstringSet by the APIThe User-Agent header the service sends when it fetches input_url, at most 256 characters.
webhook_urlstring""The URL that receives the job's outcome and refund updates. It must be an HTTP or HTTPS URL without credentials, at most 2,048 characters. See Webhooks.

Unknown parameters fail validation with a 400, including max_input_duration. The service sets it from what you pay.

Example Requests​

Request a Quote​

curl --include --request POST \
--url https://api.speechischeap.com/v2/x402/jobs/ \
--header 'Content-Type: application/json' \
--data '{
"input_url": "https://example.com/audio-file.mp3",
"input_duration": 200,
"webhook_url": "https://your-domain.com/webhook"
}'

The response is the 402 quote. A request with a signature needs a wallet, so use an x402 client for the paid request.

Pay with the Example Client​

x402-client.mjs is a standalone Node.js 22 script built on @x402/fetch. Install the pinned packages and set SIC_X402_WALLET_MODULE to the path of a module that exports signer, an EVM account with address and signTypedData, such as a viem account. Keep wallet keys in your wallet setup, not in the request JSON or the script.

npm install @x402/core@2.28.0 @x402/evm@2.28.0 @x402/fetch@2.28.0 viem@2.57.7

A minimal wallet module, wallet.mjs, that reads a private key from the environment:

import { privateKeyToAccount } from 'viem/accounts';

export const signer = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY);

Save the request body as request.json, create a private directory for state files, then run the script with a new state file and a price ceiling in US dollars for the whole purchase:

mkdir -p -m 700 "$HOME/.local/state/sic"
SIC_X402_WALLET_MODULE=./wallet.mjs node x402-client.mjs request.json "$HOME/.local/state/sic/purchase-001.json" 0.01

The script signs only a native USDC quote on Base that is within your ceiling. It saves the proof to the state file before it sends the paid request, then replays the same proof every five seconds, for up to five minutes, while the payment is pending. It prints the accepted response as JSON and keeps any upload token in the state file instead of printing it.

To resume after a crash or a lost response, pass - instead of the request file. The script reads the original request from the state file and replays the same proof:

SIC_X402_WALLET_MODULE=./wallet.mjs node x402-client.mjs - "$HOME/.local/state/sic/purchase-001.json" 0.01
  • The saved ceiling still applies, so a replay can't accept a changed price.
  • If the service declines the proof, the script deletes it, because the service took no payment. Fix the cause, then run the same command again to sign a new quote.
  • After a 404 or 410, the service took no payment. Start a new purchase with a new state file.
  • After a crash, read the PID in the .lock file next to the state file. Confirm that process stopped, then remove only that stale lock.
  • Never print the state file, a reusable proof, or an upload token.

Response​

The service uses standard HTTP status codes to indicate the outcome of your request.

Status CodeDescription
202 AcceptedThe service has your proof. The body is a pending payment, the accepted job, or a purchase that ended without a job.
400 Bad RequestMalformed JSON, an invalid option, or a malformed payment proof, with a plain-text message
402 Payment RequiredWithout a signature: the quote. With a signature: the service rejected the proof or declined the settlement and took no payment. See Declined Payments.
404 Not FoundThe quote is unknown, or it expired unpaid and the service removed it. The service took no payment.
409 ConflictThe request or proof conflicts with the original purchase
410 GoneThe quote expired, or the payment authorization ended without settlement. The service took no payment.
413 Content Too LargeThe body is larger than 16 KB
429 Too Many RequestsToo many unpaid quote requests from one IP address, about 20 per minute. Wait for the Retry-After delay. Paid requests are never limited.
503 Service Unavailablex402 payments are switched off, not configured, or temporarily unavailable

Replaying the same paid request never charges you again. Keep the proof, and replay it after an error or a timeout.

Pending Payment​

When the payment isn't confirmed yet, the 202 has no job id. This is the usual first answer to a paid request. Send the same request and proof again.

{
"purchase_id": "00000000-1111-4222-8333-444444444444",
"status": "PENDING",
"payment_status": "stripe_pending"
}

Once the transfer succeeds, the response carries a PAYMENT-RESPONSE header with the settlement, including the transaction hash, even while the payment confirmation is pending. The possible payment_status values are in States.

Accepted Job​

Once the payment is confirmed, the same request returns the job:

{
"id": "00000000-1111-4222-8333-555555555555-sic",
"output": {},
"status": "PENDING",
"purchase_id": "00000000-1111-4222-8333-444444444444"
}

Read status rather than assuming PENDING. Use the id to get the job or to cancel it. The service starts the transcription only after the payment is confirmed.

Purchase That Ended Without a Job​

The service checks input_url only after you pay. If it can't fetch the file, the purchase fails without a job id, and the service refunds the payment in full:

{
"purchase_id": "00000000-1111-4222-8333-444444444444",
"status": "FAILED",
"admission_expires_at": "2026-09-30T12:05:00.000Z",
"output": {
"error": "Input media could not be reached"
}
}

There's no job to read, so replay the request or use your webhook to see this outcome.

Errors​

The errors for this endpoint are listed under x402 API errors. The most likely are a 400 for an invalid input_duration, a 429 when you request too many unpaid quotes, and a 402 with a fresh PAYMENT-REQUIRED header when the service declines your proof. Failures after the service accepts a job are listed under failures after a job is accepted.

Notes​

  • Save the request, the quote, and the signed proof before you send the paid request, and keep them private. Recover a Pending Purchase explains how to resume.
  • Send the same request fields in the paid request as in the quote request. A custom client must also sign the quoted requirement unchanged and keep its resource and accepted fields. Otherwise the service answers 409.
  • The @x402 SDK refuses payments above $1 unless you raise its spend limit with setSpendControls. A long file or extra add-ons can cost more.
  • Send the paid request right after you sign it, from a machine with an accurate clock. A validBefore outside the allowed window gets a 402 with a fresh PAYMENT-REQUIRED header and no payment.
  • A file shorter than input_duration can cost less, and the service refunds the difference. See Excess Refunds.
  • A 503 can mean three things. x402 payments are not enabled means x402 is switched off. The service refuses new quotes and first payments until it's switched back on, so don't retry in a loop. A purchase you already paid for continues. x402 payment configuration is incomplete is a service-side problem. Try again later, and email support@speechischeap.com if it lasts. A JSON message that says payment admission is temporarily unavailable is transient: retry the original request and any proof, and don't pay again.