Cancel a Job
Use this request to cancel a job that is still pending. You sign in with the wallet that paid for the job instead of an API key. A confirmed cancellation refunds the payment in full. To cancel an API-key job, see Cancel a Job in the Jobs API.
Endpoint
DELETE https://api.speechischeap.com/v2/x402/jobs/:job_id
Don't add a trailing slash. A request to /v2/x402/jobs/:job_id/ returns 404 Not Found.
Authentication
Cancellation uses the standard Sign-In With X (SIWX) extension of x402. It isn't a payment, and it doesn't use an API key. It takes two requests:
- Send the
DELETEwithout aSIGN-IN-WITH-Xheader. The response is402with a base64-encodedPAYMENT-REQUIREDheader. It's an auth-only challenge withaccepts: []and asign-in-with-xextension. The challenge is scoped to this job and this action, has a one-use nonce, and expires after 300 seconds. Requesting it doesn't cancel the job or charge the wallet. - Sign the challenge with the paying wallet. Send the
DELETEagain with the proof in theSIGN-IN-WITH-Xheader.
The service checks the challenge and that the signer is the wallet that paid. A public job ID or transaction hash isn't enough. Each verified challenge works once, so request a new one before you retry. The signer must support EIP-191 signMessage.
An API-key DELETE can't cancel an x402 job. It returns 403.
Request
| Header | Description |
|---|---|
SIGN-IN-WITH-X | The signed challenge, encoded by the SDK. Send it on the second request only. |
Example Requests
Request the Challenge
curl --include --request DELETE \
--url https://api.speechischeap.com/v2/x402/jobs/00000000-1111-4222-8333-555555555555-sic
Cancel with the Paying Wallet
Install the packages. This Node.js 22 example uses the same SIC_X402_WALLET_MODULE as the example client, and its signer must be the wallet that paid:
npm install @x402/core@2.28.0 @x402/extensions@2.28.0
import { pathToFileURL } from 'node:url';
import { x402Client, x402HTTPClient } from '@x402/core/client';
import { createSIWxPayload, encodeSIWxHeader } from '@x402/extensions/sign-in-with-x';
// The signer that paid for the job. It needs signMessage, not only signTypedData.
const { signer } = await import(pathToFileURL(process.env.SIC_X402_WALLET_MODULE).href);
const jobId = '00000000-1111-4222-8333-555555555555-sic';
const url = `https://api.speechischeap.com/v2/x402/jobs/${encodeURIComponent(jobId)}`;
// Request the challenge.
const challenge = await fetch(url, { method: 'DELETE', redirect: 'error' });
if (challenge.status !== 402) throw new Error('Cancellation challenge unavailable');
const http = new x402HTTPClient(new x402Client());
const required = http.getPaymentRequiredResponse(name => challenge.headers.get(name));
const extension = required.extensions['sign-in-with-x'];
const chain = extension.supportedChains.find(item => item.chainId === 'eip155:8453' && item.type === 'eip191');
if (required.accepts.length || required.resource.url !== url || !chain || extension.info.uri !== url
|| extension.info.statement !== `Cancel Speech is Cheap transcription ${jobId}`
|| !extension.info.resources?.includes(`urn:speechischeap:cancel:${jobId}`)) throw new Error('Invalid auth-only challenge');
// Sign it with the paying wallet and send the proof.
const proof = await createSIWxPayload({ ...extension.info, ...chain }, signer, url);
const response = await fetch(url, {
method: 'DELETE', redirect: 'error',
headers: { 'SIGN-IN-WITH-X': encodeSIWxHeader(proof) }
});
console.log(response.status, await response.json());
Response
The service uses standard HTTP status codes to indicate the outcome of your request.
| Status Code | Description |
|---|---|
| 200 OK | The cancellation is confirmed. The service refunds the payment in full. |
| 202 Accepted | The service recorded the request but hasn't confirmed the cancellation. |
| 401 Unauthorized | The proof is invalid, expired, already used, or for another job. Request a new challenge. |
| 402 Payment Required | The challenge, when you send no SIGN-IN-WITH-X header. The service charges nothing. |
| 403 Forbidden | The signer isn't the wallet that paid. |
| 404 Not Found | No paid job has this ID. |
| 409 Conflict | The job already completed, failed, or was canceled. |
| 503 Service Unavailable | Cancellation is temporarily unavailable. Try again. |
Confirmed
{
"id": "00000000-1111-4222-8333-555555555555-sic",
"output": {
"error": "Transcription canceled"
},
"status": "CANCELED",
"cancellation_status": "confirmed"
}
Requested
The job may still finish. Check Get a Job, or wait for the job's outcome and refund updates at your webhook.
{
"id": "00000000-1111-4222-8333-555555555555-sic",
"output": {},
"status": "PENDING",
"cancellation_status": "requested"
}
Already Final
status is the job's final status, and cancellation_status is terminal:
{
"id": "00000000-1111-4222-8333-555555555555-sic",
"status": "COMPLETED",
"cancellation_status": "terminal"
}
Errors
The errors for this endpoint are listed under x402 API errors. The most likely are a 401 for a challenge that expired or was already used, a 403 for a signer that isn't the paying wallet, and a 409 for a job that already finished.
Notes
- Use the job
id, not thepurchase_id. - Only a pending job can be canceled. A request for a job that already finished returns
409and doesn't change its payment.