> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useselo.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Run a preflight

> Check, for free, that a target can be tested before you pay. Selo checks the authorization, sends one unpaid request to the target (with `requestBody` for `POST`, 10 second timeout, redirects not followed), reads its x402 v2 challenge, and requires an `exact` USDC payment on Algorand priced at or below the per-job spend limit (0.50 USDC). It also looks the target up in the Bazaar catalog and stores what it finds with the preflight. An eligible preflight is valid for 15 minutes.



## OpenAPI

````yaml /openapi.json post /v1/preflight
openapi: 3.1.0
info:
  title: Selo API
  version: 1.0.0
  summary: CI for x402. It pays your endpoint before your users do.
  description: >-
    Selo is a release gate for paid x402 APIs on Algorand. You authorize your
    endpoint, run a free preflight, then pay Selo 1.00 USDC over x402. Only
    after that payment settles does Selo pay your endpoint as a real x402
    client, run five release checks and return a deterministic PASS, FAIL or
    INCONCLUSIVE verdict with an Ed25519-signed attestation.


    There are no API keys. Access to a target is granted per target by
    authorization; the only paid route is `POST /v1/release-test`, paid with
    x402. Bodies are JSON, at most 64 KiB.
  contact:
    name: Selo
    url: https://useselo.xyz
servers:
  - url: https://api.useselo.xyz
    description: Production, Algorand Mainnet
security: []
tags:
  - name: Authorizations
    description: Prove you control the target before Selo sends it anything.
  - name: Preflight
    description: Check for free that a target can be tested and is within budget.
  - name: Release tests
    description: The paid x402 route that runs the release checks.
  - name: Reports
    description: Private, token-addressed reports of finished release tests.
  - name: Attestation keys
    description: Public keys that verify signed verdicts.
  - name: Discovery
    description: Machine-readable descriptions of Selo.
  - name: Health
    description: Liveness.
paths:
  /v1/preflight:
    post:
      tags:
        - Preflight
      summary: Run a preflight
      description: >-
        Check, for free, that a target can be tested before you pay. Selo checks
        the authorization, sends one unpaid request to the target (with
        `requestBody` for `POST`, 10 second timeout, redirects not followed),
        reads its x402 v2 challenge, and requires an `exact` USDC payment on
        Algorand priced at or below the per-job spend limit (0.50 USDC). It also
        looks the target up in the Bazaar catalog and stores what it finds with
        the preflight. An eligible preflight is valid for 15 minutes.
      operationId: createPreflight
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PreflightRequest'
            examples:
              post:
                summary: POST route with a body
                value:
                  authorizationId: auth_…
                  targetUrl: https://api.example.com/v1/orders
                  method: POST
                  requestBody:
                    sku: demo-widget
                    quantity: 1
              get:
                summary: GET route
                value:
                  authorizationId: auth_…
                  targetUrl: https://api.example.com/v1/quote
                  method: GET
      responses:
        '201':
          description: The target is eligible.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreflightEligible'
              examples:
                eligible:
                  summary: Eligible
                  value:
                    eligible: true
                    preflightId: pfl_…
                    target:
                      url: https://api.example.com/v1/orders
                      method: POST
                      priceUsdc: '0.01'
                    estimatedMaxSpendUsdc: '0.01'
                    seloPriceUsdc: '1.00'
                    expiresAt: '2026-10-26T12:15:00.000Z'
        '400':
          description: >-
            The body fails validation (`ApiError`), or the target URL is invalid
            or not https (`PreflightIneligible`, `preflightId` is `null`).
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: '#/components/schemas/PreflightIneligible'
                  - $ref: '#/components/schemas/ApiError'
              examples:
                TARGET_URL_INVALID:
                  summary: TARGET_URL_INVALID
                  value:
                    eligible: false
                    preflightId: null
                    reason: TARGET_URL_INVALID
                    message: The target URL is not a valid absolute URL
                TARGET_NOT_HTTPS:
                  summary: TARGET_NOT_HTTPS
                  value:
                    eligible: false
                    preflightId: null
                    reason: TARGET_NOT_HTTPS
                    message: Only https targets can be tested
                VALIDATION_FAILED:
                  summary: Body on a GET
                  value:
                    error: VALIDATION_FAILED
                    message: |-
                      ✖ requestBody is only allowed with POST
                        → at requestBody
        '403':
          description: >-
            No verified, unexpired authorization covers this origin, path and
            method. `preflightId` is `null` when the authorization ID is
            unknown.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreflightIneligible'
              examples:
                AUTHORIZATION_MISSING:
                  summary: AUTHORIZATION_MISSING
                  value:
                    eligible: false
                    preflightId: pfl_…
                    reason: AUTHORIZATION_MISSING
                    message: >-
                      No verified authorization covers this target origin, route
                      and method
                AUTHORIZATION_EXPIRED:
                  summary: AUTHORIZATION_EXPIRED
                  value:
                    eligible: false
                    preflightId: pfl_…
                    reason: AUTHORIZATION_EXPIRED
                    message: >-
                      The authorization is not verified or has expired; verify
                      it again
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          description: >-
            The target cannot be tested. Probe rejections carry a stored
            `preflightId`; a blocked hostname found while parsing the URL has
            `preflightId: null`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreflightIneligible'
              examples:
                TARGET_ADDRESS_BLOCKED:
                  summary: TARGET_ADDRESS_BLOCKED
                  value:
                    eligible: false
                    preflightId: pfl_…
                    reason: TARGET_ADDRESS_BLOCKED
                    message: >-
                      The target must be a public hostname on the default https
                      port
                TARGET_UNREACHABLE:
                  summary: TARGET_UNREACHABLE
                  value:
                    eligible: false
                    preflightId: pfl_…
                    reason: TARGET_UNREACHABLE
                    message: >-
                      The target could not be reached or answered with a server
                      error
                NO_PAYMENT_CHALLENGE:
                  summary: NO_PAYMENT_CHALLENGE
                  value:
                    eligible: false
                    preflightId: pfl_…
                    reason: NO_PAYMENT_CHALLENGE
                    message: >-
                      The target did not answer the unpaid request with a valid
                      x402 challenge
                NETWORK_NOT_SUPPORTED:
                  summary: NETWORK_NOT_SUPPORTED
                  value:
                    eligible: false
                    preflightId: pfl_…
                    reason: NETWORK_NOT_SUPPORTED
                    message: >-
                      The target offers no exact payment on the Algorand network
                      Selo pays on
                ASSET_NOT_SUPPORTED:
                  summary: ASSET_NOT_SUPPORTED
                  value:
                    eligible: false
                    preflightId: pfl_…
                    reason: ASSET_NOT_SUPPORTED
                    message: >-
                      The target offers no exact payment in USDC on the Algorand
                      network
                PRICE_OVER_BUDGET:
                  summary: PRICE_OVER_BUDGET
                  value:
                    eligible: false
                    preflightId: pfl_…
                    reason: PRICE_OVER_BUDGET
                    message: >-
                      The target price exceeds the per-job downstream spend
                      limit
        '500':
          $ref: '#/components/responses/InternalError'
components:
  schemas:
    PreflightRequest:
      type: object
      additionalProperties: false
      required:
        - authorizationId
        - targetUrl
        - method
      properties:
        authorizationId:
          type: string
          maxLength: 64
          description: A verified authorization that covers this origin, path and method.
        targetUrl:
          type: string
          maxLength: 2048
          format: uri
          description: >-
            Absolute `https` URL of the paid route. A query string is sent to
            the target; coverage is checked on origin and path.
        method:
          $ref: '#/components/schemas/HttpMethod'
        requestBody:
          description: >-
            JSON body Selo sends to the target on every request of this test.
            Only with `POST`. Any JSON value except `null`, at most 16 KiB
            (16384 bytes) once serialized. Omit it to send no body.
          type:
            - object
            - array
            - string
            - number
            - boolean
    PreflightEligible:
      type: object
      description: >-
        The target can be tested. Pass `preflightId` to `POST /v1/release-test`
        before `expiresAt`.
      required:
        - eligible
        - preflightId
        - target
        - estimatedMaxSpendUsdc
        - seloPriceUsdc
        - expiresAt
      properties:
        eligible:
          type: boolean
          const: true
        preflightId:
          type: string
          description: '`pfl_` plus 26 Crockford base32 characters.'
        target:
          type: object
          required:
            - url
            - method
            - priceUsdc
          properties:
            url:
              type: string
              description: Normalized target URL (fragment removed).
            method:
              $ref: '#/components/schemas/HttpMethod'
            priceUsdc:
              type: string
              pattern: ^[0-9]+\.[0-9]{2,6}$
              description: Price the target asked for in its 402 challenge.
        estimatedMaxSpendUsdc:
          type: string
          pattern: ^[0-9]+\.[0-9]{2,6}$
          description: >-
            What Selo expects to pay the target: one paid request at the
            target's price.
        seloPriceUsdc:
          type: string
          pattern: ^[0-9]+\.[0-9]{2,6}$
          description: What the release test costs you, paid to Selo over x402.
        expiresAt:
          type: string
          format: date-time
          description: The preflight is valid for 15 minutes.
    PreflightIneligible:
      type: object
      description: The target cannot be tested. Nothing was paid.
      required:
        - eligible
        - preflightId
        - reason
        - message
      properties:
        eligible:
          type: boolean
          const: false
        preflightId:
          type:
            - string
            - 'null'
          description: >-
            ID of the stored preflight, or `null` when Selo refused the request
            before storing it (invalid URL, blocked hostname, unknown
            authorization).
        reason:
          $ref: '#/components/schemas/PreflightRejection'
        message:
          type: string
    ApiError:
      type: object
      description: Error body returned by every failing request.
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: Stable machine-readable error code, for example `VALIDATION_FAILED`.
        message:
          type: string
          description: Human-readable explanation. Do not parse it.
    HttpMethod:
      type: string
      enum:
        - GET
        - POST
      description: HTTP method Selo uses to call the target.
    PreflightRejection:
      type: string
      enum:
        - AUTHORIZATION_MISSING
        - AUTHORIZATION_EXPIRED
        - TARGET_URL_INVALID
        - TARGET_NOT_HTTPS
        - TARGET_ADDRESS_BLOCKED
        - METHOD_NOT_ALLOWED
        - TARGET_UNREACHABLE
        - NO_PAYMENT_CHALLENGE
        - NETWORK_NOT_SUPPORTED
        - ASSET_NOT_SUPPORTED
        - PRICE_OVER_BUDGET
      description: Why a target is not eligible for a release test.
  responses:
    PayloadTooLarge:
      description: The request body is larger than 64 KiB (65536 bytes).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          examples:
            PAYLOAD_TOO_LARGE:
              summary: Body over 64 KiB
              value:
                error: PAYLOAD_TOO_LARGE
                message: Request body exceeds 65536 bytes
    InternalError:
      description: Unexpected server error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          examples:
            INTERNAL:
              summary: Unexpected server error
              value:
                error: INTERNAL
                message: Internal server error

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.