Skip to main content
A preflight is free. Selo sends one unpaid request to your endpoint, reads its 402 and decides whether a paid release test can run within budget. An eligible preflight returns a pfl_… id that stays valid for 15 minutes; the paid release test must name it.

What preflight checks

Selo runs these checks in order and stops at the first one that fails.
1

The target URL

An absolute https URL on a public hostname, on the default port, with no username or password.
2

The authorization

The authorization exists, is VERIFIED, has not expired, and covers the exact origin, path and method. Nothing is sent to your endpoint if this fails. See Authorize your endpoint.
3

One unpaid request

Selo sends your method and, for POST, your JSON body with content-type: application/json and an Idempotency-Key: preflight-<preflight id> header. It resolves DNS, refuses the request if any answer is not a public address, pins the connection to the vetted address, waits at most 10 seconds and does not follow redirects.
4

The 402 challenge

The answer must be a 402 whose PAYMENT-REQUIRED header decodes to an x402 version 2 challenge. It must offer an exact requirement on Algorand Mainnet in USDC (ASA 31566704) with a positive integer amount and a valid Algorand payTo address.
5

The price

The price of that requirement must be at most 0.50 USDC, the most Selo spends on a target in one job. Exactly 0.50 is accepted.
6

A Bazaar snapshot

Selo looks your route up in the Bazaar catalog among the listings of the payTo in your 402 and stores what it finds, for the discovery check later. A missing listing or an unreachable catalog never makes a preflight ineligible.
Your endpoint receives exactly one unpaid request per preflight. Nothing is signed and nothing is paid.

Prerequisites

  • A verified authorization for the exact URL and method. See Authorize your endpoint.
  • For POST, the JSON body Selo should send. The same body is used for the paid request and for the replay in the release test.

Run a preflight with the CLI

1

Write the request body (POST only)

body.json
The body is any JSON value except a top-level null, at most 16 KiB once serialized. A GET takes no body.
2

Run it

Terminal
3

Read the terms

Output
The command exits 0 when the target is eligible.
selo test runs its own fresh preflight on every run, so you never pass a pfl_… id to it. Use selo preflight to check eligibility and terms without a wallet, for example in a free CI stage. See Gate deploys in GitHub Actions.

The API call

Terminal
string
required
The verified auth_… id, at most 64 characters.
string
required
The https URL of your paid route, at most 2048 characters.
string
required
GET or POST.
any JSON value
POST only. Any JSON value except a top-level null, at most 16 KiB once serialized. Omit it to send no body.
Unknown fields are rejected with 400 VALIDATION_FAILED, and the whole request may be at most 64 KiB.

Eligible

An eligible preflight answers 201 Created:
201 Created
boolean
true.
string
The id the paid release test must name.
object
string
The most Selo will spend on your endpoint in this job. A quick test pays once; the replay re-sends the same signed payment.
string
What the release test costs you.
string
When the preflight stops being payable.

Ineligible

An ineligible preflight answers with the HTTP status of its reason and this body:
422 Unprocessable Entity
preflightId is null when Selo refused the request before it recorded a preflight: for a URL that breaks the URL rules (invalid, not https, or a blocked hostname or port), or for an unknown authorization id. Every other rejection is recorded and carries its id. GET /v1/preflight/<pfl id> returns the stored eligible or ineligible view of any preflight.

Rejection reasons

Other errors use the standard error body, with an error code and a message:

Exit codes

Validity

A preflight is valid for 15 minutes. After that, POST /v1/release-test answers 409 PREFLIGHT_EXPIRED before any payment is taken, and you run a new preflight. The paid route also re-checks the preflight before it asks for payment: an unknown id is 404 PREFLIGHT_NOT_FOUND, an ineligible one is 422 PREFLIGHT_INELIGIBLE, an authorization that is no longer verified is 403 AUTHORIZATION_INVALID, and a price above the job budget is 422 PRICE_OVER_BUDGET. See Pay with any x402 client.

Troubleshooting

Selo expects a paid route to answer an unpaid request with 402. A 200 means the route is not protected, and the preflight is NO_PAYMENT_CHALLENGE. Check that your x402 middleware covers the exact path and method you are testing.
Preflight does not follow redirects, so the redirect itself is the answer and the preflight is NO_PAYMENT_CHALLENGE. Authorize and test the final URL.
That is fine. Selo picks the exact requirement on Algorand Mainnet in USDC and ignores the others.
The probe carries the body you give with --body or requestBody. Send the same body your clients send, so the route reaches its payment check.

Next

Write a release contract

Declare what a paid response must look like.

Run a release test

Pay once and gate on the verdict.