Skip to main content
The selo CLI is a convenience. Selo’s paid route is an ordinary x402 v2 resource on Algorand, so any x402 client that can pay an exact USDC requirement can run a release test. This page walks through the raw HTTP flow and shows a client built on the same x402 packages Selo uses.

Prerequisites

  • An x402 v2 client that can sign an exact payment on Algorand Mainnet in USDC (ASA 31566704).
  • A payer wallet opted in to USDC that holds at least 1.00 USDC per test.
  • Your endpoint’s URL and method, and a way to serve a file on its origin.

Steps

1

Authorize the target

Terminal
Serve the returned expectedContent line at the returned verificationUrl, then verify:
Terminal
The answer must show "status": "VERIFIED". See Authorize your endpoint for every field and error.
2

Run a preflight

Terminal
Keep preflightId from the 201 answer. You have 15 minutes to pay. See Run a preflight.
3

Build the release test body

release-test.json
string
required
The eligible preflight, at most 64 characters.
string
required
quick, the only profile.
object
The release contract: optional status, contentType and jsonSchema. It is compiled before you are asked to pay. Without it, the paid response is only checked for status 200. See Write a release contract.
string
The release you are testing, such as a Git SHA: 1 to 128 characters of A-Z, a-z, 0-9, ., _ or -. It is signed into the attestation as asserted_by_client.
Unknown fields are rejected, and the request body may be at most 64 KiB.
4

Read Selo's 402

Send the body without a payment. Selo validates it, checks the preflight and its authorization, and only then answers 402 with the challenge in the PAYMENT-REQUIRED header, base64-encoded JSON:
Terminal
This is Selo’s live challenge as captured from the public API, without its extensions (bazaar, with the input and output schema of the route, and x402-merchant, with Selo’s name and categories):
PAYMENT-REQUIRED (decoded)
If the body, the preflight or the authorization is wrong, you get that error instead of a 402, and nothing is charged. See Errors.
5

Pay and send the request again

Sign the requirement with your x402 client and send the same body again with two extra headers:Selo verifies the payment, claims the job under your key, settles the payment and records it, and only then pays your endpoint. The answer is the report, with Selo’s settlement in the PAYMENT-RESPONSE header.

A client on Selo’s own x402 packages

Selo pays targets, and its CLI pays Selo, with the official x402 packages: @x402/core for the client and @x402/avm for the Algorand exact scheme, at version 2.27.0, with algosdk 3.8.0 to load the wallet. This script uses the same calls. It refuses to sign anything but the payment you approved.
Terminal
pay-selo.mts
Terminal
Keep the mnemonic out of your code, your logs and your shell history. Send the paid request with redirect: "manual", as above, so a signed payment never follows a redirect, and never retry a payment with a new Idempotency-Key while an earlier outcome is unknown.

The response

A finished test answers 200. This is the fixed-v2 result from the demo merchant’s Mainnet run on 26 October 2026, rebuilt from that run’s published summary and attestation, with the report token replaced:
200 OK
The response also carries attestation, the signed envelope described in Verify a signed verdict.
string
The release job.
string
PASS, FAIL or INCONCLUSIVE.
string
The tested URL.
object[]
One result per check: id, status (PASS, WARN, FAIL or INCONCLUSIVE), code, blocking, summary and the evidence ids. See The five release checks.
string[]
Concerns that do not block, such as a WARN.
string | null
Why the verdict is INCONCLUSIVE, otherwise null.
object
seloInboundTxId, the transaction in which you paid Selo; downstreamSpendUsdc, what Selo spent on the target; downstreamTxIds, the transactions in which Selo paid it.
string
The private report link. Anyone who has it can read the report, so keep it secret.
object
The Ed25519-signed attestation, when Selo has a signing key.
Recompute the verdict from checks before you trust it: any blocking FAIL with evidence is FAIL; otherwise any blocking INCONCLUSIVE, missing check or check without evidence is INCONCLUSIVE; otherwise PASS. That is what the CLI does. See Verdicts and the gate.

Retries

Send every retry of the same logical request with the same Idempotency-Key and the same preflightId. Selo’s 402 still asks for a payment on a retry, so your client signs one, but Selo uses the key to find the existing job and never settles the new payment: Retries work while the preflight is valid. After 15 minutes the route answers 409 PREFLIGHT_EXPIRED before it looks for the job. If you send no Idempotency-Key, Selo derives the key from the payment payload itself, so only the identical signed payment is recognized as the same request, and a newly signed retry would be a new paid test.

Errors

The route checks in this order, and everything before the settlement is free: See Held payments and reconciliation for what Selo does with a held settlement, and Errors and reason codes for every code.

Next

API overview

Every route, request and response.

Run a release test

The CLI does all of this, plus recovery and exit codes.