# Quali API for Agents

Quali gives agents human taste on-demand: create polls to ask World ID-verified humans a question using text or images, pay per response with x402, and retrieve aggregate results.
Payment is per request with x402 V2. There are no API keys.

- Base URL: https://www.getquali.com/api/v1
- Paid endpoint: POST https://www.getquali.com/api/v1/polls
- OpenAPI discovery: https://www.getquali.com/openapi.json
- Payment protocol: x402 V2 exact scheme
- Production facilitator: https://api.cdp.coinbase.com/platform/v2/x402
- Request payment header: PAYMENT-SIGNATURE
- 402 response header: PAYMENT-REQUIRED
- Success response header: PAYMENT-RESPONSE
- Accepted settlement asset: USDC
- Accepted networks by default: Base (eip155:8453) and World Chain (eip155:480)
- Discovery metadata: 402 responses include resource.serviceName, resource.tags, resource.iconUrl, and extensions.bazaar
- x402scan discovery: /openapi.json includes info.x-guidance, request/response schemas, and x-payment-info
- Minimum poll size: 100 responses
- Price: 0.005 USDC per requested response
- Voter pool: 0.0005 USDC per completed vote

Server production env:

- CDP_API_KEY_ID
- CDP_API_KEY_SECRET
- X402_PAY_TO_ADDRESS_BASE
- X402_PAY_TO_ADDRESS_WORLDCHAIN
- X402_RESOURCE_ICON_URL optional
- X402_FACILITATOR_URL optional override

## Flow

1. Quote: GET /api/v1/quote?maxResponses=100
2. Read the returned paymentRequired.accepts array. It contains Base and World Chain USDC requirements unless a network filter is supplied.
3. Create a V2 x402 PaymentPayload from the returned paymentRequired object with an official x402 client and the chosen accepted requirement. Preserve paymentPayload.resource.url.
4. POST /api/v1/polls with Content-Type: application/json and PAYMENT-SIGNATURE: <base64 payload>.
5. Store readToken from the response. It is required for results and votes.
6. Decode PAYMENT-RESPONSE from the success response if you need settlement proof.

Network filters accepted by /quote:

- base or eip155:8453
- worldchain, world-chain, world, or eip155:480
- base-sepolia or eip155:84532 if enabled
- worldchain-sepolia or eip155:4801 if enabled

## Quote

GET /api/v1/quote?maxResponses=100&network=worldchain

Example response:

```json
{
  "maxResponses": 100,
  "costUsdc": 0.5,
  "costAtomic": "500000",
  "rewardPoolUsdc": 0.05,
  "qualiFeeUsdc": 0.45,
  "x402Version": 2,
  "paymentRequired": {
    "x402Version": 2,
    "resource": {
      "url": "https://www.getquali.com/api/v1/polls",
      "description": "Human taste on-demand: create text or image polls for World ID-verified humans, pay per response with x402, and retrieve aggregate results through the Quali API.",
      "mimeType": "application/json",
      "serviceName": "Quali",
      "tags": ["taste", "polls", "feedback", "world-id", "agents"],
      "iconUrl": "https://www.getquali.com/favicon.ico"
    },
    "extensions": {
      "bazaar": {
        "info": {
          "input": {
            "type": "http",
            "method": "POST",
            "bodyType": "json",
            "body": {
              "question": "Which logo should our note-taking app use?",
              "description": "Pick the logo that feels more trustworthy, memorable, and useful for a note-taking app.",
              "options": [
                { "text": "Logo A" },
                { "text": "Logo B" }
              ],
              "maxResponses": 100,
              "expiresAt": "2099-01-15T12:00:00.000Z"
            }
          },
          "output": {
            "type": "json",
            "example": {
              "id": "uuid",
              "url": "https://www.getquali.com/polls/<id>",
              "payment": { "network": "eip155:8453", "amountUsdc": 0.5 },
              "pricing": { "maxResponses": 100, "costUsdc": 0.5 }
            }
          }
        },
        "schema": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "type": "object",
          "required": ["input"]
        }
      }
    },
    "accepts": [{
      "scheme": "exact",
      "network": "eip155:480",
      "amount": "500000",
      "asset": "0x79A02482A880bCE3F13e09Da970dC34db4CD24d1",
      "payTo": "0x...",
      "maxTimeoutSeconds": 120,
      "extra": { "name": "USDC", "version": "2" }
    }]
  }
}
```

If a paid request is missing or has an invalid payment, Quali returns HTTP 402 with the same paymentRequired object in the JSON body and a PAYMENT-REQUIRED header.

## Bazaar Discovery

Quali declares the x402 Bazaar extension on POST /api/v1/polls. The declaration includes:

- HTTP method: POST
- Body type: JSON
- A crawler-safe example request with maxResponses=100
- JSON Schema for the request body
- A JSON response example

CDP Bazaar can index the endpoint after a successful CDP-facilitated settlement for the advertised resource URL.

## Create Poll

POST /api/v1/polls requires PAYMENT-SIGNATURE.

Request body:

```json
{
  "question": "Which name is clearer?",
  "description": "Optional context for voters",
  "moreInfoUrl": "https://example.com/context",
  "options": [{ "text": "Option A" }, { "text": "Option B" }],
  "maxResponses": 100,
  "expiresAt": "2099-01-15T12:00:00Z",
  "mediaUrl": null,
  "mediaType": null,
  "layoutType": "grid",
  "callbackUrl": "https://example.com/quali-webhook"
}
```

Rules:

- 2 to 10 options.
- question <= 200 characters.
- description <= 1000 characters.
- each option text <= 120 characters.
- maxResponses must be an integer from 100 to 1,000,000.
- expiresAt must be an ISO timestamp more than 5 minutes in the future.
- callbackUrl is optional, must be HTTPS, and receives a signed poll.completed webhook when the poll completes.
- Content is moderated before payment settlement. Violations are rejected without settling payment.
- For Bazaar discovery, Quali also rejects paid requests whose PAYMENT-SIGNATURE does not include the same paymentPayload.resource.url advertised by PAYMENT-REQUIRED.

Success response:

```json
{
  "id": "uuid",
  "url": "https://www.getquali.com/polls/<id>",
  "readToken": "uuid",
  "payment": {
    "network": "eip155:480",
    "networkLabel": "World Chain",
    "txHash": "0x...",
    "payer": "0x...",
    "amountUsdc": 0.5,
    "amountAtomic": "500000"
  },
  "pricing": {
    "maxResponses": 100,
    "rewardPoolUsdc": 0.05,
    "qualiFeeUsdc": 0.45,
    "costUsdc": 0.5
  },
  "endpoints": {
    "poll": "https://www.getquali.com/api/v1/polls/<id>",
    "results": "https://www.getquali.com/api/v1/polls/<id>/results",
    "votes": "https://www.getquali.com/api/v1/polls/<id>/votes"
  }
}
```

## Read Endpoints

- GET /api/v1/polls/{id}: public poll metadata for settled API polls.
- GET /api/v1/polls/{id}/results: requires X-Read-Token or Authorization: Bearer <readToken>.
- GET /api/v1/polls/{id}/votes?limit=500&cursor=ISO: requires the read token.
- GET /api/v1/polls?payer=0x...: lists settled API polls paid by a wallet.

## Webhook

If callbackUrl was provided, Quali sends:

- Method: POST
- Event header: X-Quali-Event: poll.completed
- Delivery header: X-Quali-Delivery: <uuid>
- Signature header if QUALI_CALLBACK_SECRET is configured: X-Quali-Signature: sha256=<hmac>
- Signature base string: <deliveryId>.<raw JSON body>

Webhook body includes pollId, status, voteCount, maxResponses, completedAt, resultsUrl, votesUrl, and readToken.

## Errors

| Status | Meaning |
|---|---|
| 400 | Malformed request or content moderation rejection. |
| 401 | Missing read token on private endpoint. |
| 402 | Payment required or payment verification/settlement failed. |
| 403 | Read token does not match this poll. |
| 404 | Poll not found, not API-created, or not settled. |
| 409 | Payment signature was already used. |
| 503 | Server missing payment or moderation configuration. |

Full docs: https://www.getquali.com/docs
