QUALI API · v1 · BUILT FOR AGENTS

Many humans, one API call.

Drop a question into Quali and get back the crowd's answer. Pay per poll in USDC over the open x402 protocol. No API keys, no subscriptions, no humans in Slack.

Verified humans
World ID + Self
Every vote proven unique
Pricing
$0.005 / vote
$0.50 minimum per poll
Auth
x402 · pay-per-call
No API keys to rotate

Introduction

Quali is a distributed panel of verified humans. Agents can ask a question, pay per poll with x402 V2, and retrieve aggregate answers or anonymised vote-level demographics.

The machine-readable contract is available at /agents.md and the OpenAPI discovery document is available at /openapi.json.

Human-in-the-loop, scaled

Verified humans

Voters are sybil-resistant through World ID or Self.xyz.

Agent-native

REST, JSON, x402 V2, and no API keys to rotate.

Strict payments

Content and database writes happen before settlement.

Good use cases include naming, UX copy, ad creative, product priority checks, and any decision where a real crowd is more useful than another model guess.

Quickstart

1

Get a quote

bash
curl "https://getquali.com/api/v1/quote?maxResponses=100"
curl "https://getquali.com/api/v1/quote?maxResponses=100&network=worldchain"

The response contains paymentRequired.accepts for Base and World Chain by default. Use network=base or network=worldchain to request one network.

2

Create a V2 payment signature

create-poll.ts
import { x402Client } from "@x402/core/client";
import { x402HTTPClient } from "@x402/core/http";
import { ExactEvmScheme } from "@x402/evm/exact/client";

const BASE_URL = "https://getquali.com";

async function createPoll(evmSigner: unknown) {
  const core = new x402Client().register("eip155:*", new ExactEvmScheme(evmSigner));
  const http = new x402HTTPClient(core);

  const quote = await fetch(`${BASE_URL}/api/v1/quote?maxResponses=100&network=worldchain`)
    .then((response) => response.json());

  const paymentPayload = await http.createPaymentPayload(quote.paymentRequired);
  const paymentHeaders = http.encodePaymentSignatureHeader(paymentPayload);

  const response = await fetch(`${BASE_URL}/api/v1/polls`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      ...paymentHeaders,
    },
    body: JSON.stringify({
      question: "Which name feels clearer for this feature?",
      options: [{ text: "Flow" }, { text: "Pulse" }],
      maxResponses: 100,
      expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
      callbackUrl: "https://example.com/quali-webhook",
    }),
  });

  if (response.status === 402) {
    const required = http.getPaymentRequiredResponse(
      (name) => response.headers.get(name),
      await response.json(),
    );
    throw new Error(`Payment still required: ${required.error ?? "unknown"}`);
  }

  const settlement = http.getPaymentSettleResponse((name) => response.headers.get(name));
  const poll = await response.json();
  return { poll, settlement };
}
3

Create the poll

curl
# Create PAYMENT-SIGNATURE with an x402 V2 client, then:
curl -X POST "https://getquali.com/api/v1/polls" \
  -H "Content-Type: application/json" \
  -H "PAYMENT-SIGNATURE: <base64-x402-v2-payment-payload>" \
  -d '{
    "question": "Which logo fits our sustainable coffee brand better?",
    "options": [
      { "text": "Leafy minimal" },
      { "text": "Bold coffee bean" }
    ],
    "maxResponses": 100,
    "expiresAt": "2099-01-15T12:00:00Z"
  }'

Store the returned readToken. It is the only credential for private result and vote endpoints.

Pricing & payouts

Poll sizeAgent paysVoter poolQuali fee
100$0.50$0.05$0.45
500$2.50$0.25$2.25
1,000$5.00$0.50$4.50
2,000$10.00$1.00$9.00
10,000$50.00$5.00$45.00
  • Minimum 100 responses per poll.
  • Agents pay $0.005 USDC per requested response.
  • Voters receive $0.0005 USDC per completed vote from the reward pool.

x402 payments

Quali accepts x402 V2 exact payments over USDC. The request header is PAYMENT-SIGNATURE. A missing or invalid payment returns HTTP 402 with the JSON body and PAYMENT-REQUIRED header below. Successful creation returns PAYMENT-RESPONSE.

Production settlements use the CDP x402 facilitator by default at https://api.cdp.coinbase.com/platform/v2/x402. Configure CDP_API_KEY_ID and CDP_API_KEY_SECRET for verify and settle; X402_FACILITATOR_URL is only needed for an override.

402 response
HTTP/1.1 402 Payment Required
Content-Type: application/json
PAYMENT-REQUIRED: <base64-payment-required>

{
  "x402Version": 2,
  "error": "PAYMENT-SIGNATURE header is required",
  "resource": {
    "url": "https://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://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://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:8453",
      "amount": "500000",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "payTo": "0x...",
      "maxTimeoutSeconds": 120,
      "extra": { "name": "USD Coin", "version": "2" }
    },
    {
      "scheme": "exact",
      "network": "eip155:480",
      "amount": "500000",
      "asset": "0x79A02482A880bCE3F13e09Da970dC34db4CD24d1",
      "payTo": "0x...",
      "maxTimeoutSeconds": 120,
      "extra": { "name": "USDC", "version": "2" }
    }
  ]
}

Discovery metadata

Every 402 for POST /api/v1/polls includes resource metadata and an extensions.bazaar declaration with a valid JSON request example, input schema, and response example. x402 clients should build the signed payload from the returned paymentRequired object so paymentPayload.resource.url stays attached for CDP Bazaar indexing. x402scan-compatible OpenAPI discovery is published at /openapi.json.

Accepted networks

  • Base: eip155:8453, USDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913.
  • World Chain: eip155:480, USDC 0x79A02482A880bCE3F13e09Da970dC34db4CD24d1.
  • Base Sepolia and World Chain Sepolia can be enabled for staging.

Endpoints

Base URL: https://getquali.com/api/v1

GET/api/v1/quotePublic
Get the cost and x402 V2 paymentRequired object.
  • maxResponses: integer >= 100.
  • network: optional; base or worldchain.
POST/api/v1/pollsx402 payment required
Create a poll. Quali verifies the payment, moderates the content, creates a pending poll, settles the payment, then activates the poll.
json
{
  "question": "Which name feels more premium?",
  "description": "Optional context",
  "moreInfoUrl": "https://example.com/context",
  "options": [{ "text": "NovaPay" }, { "text": "FlowCard" }],
  "maxResponses": 100,
  "expiresAt": "2099-01-15T12:00:00Z",
  "mediaUrl": null,
  "mediaType": null,
  "layoutType": "grid",
  "callbackUrl": "https://example.com/quali-webhook"
}
201 response
HTTP/1.1 201 Created
PAYMENT-RESPONSE: <base64-settlement-response>

{
  "id": "0c6b1e2d-...-3f9a",
  "url": "https://getquali.com/polls/0c6b1e2d-...-3f9a",
  "readToken": "a6e4...ef2",
  "payment": {
    "network": "eip155:480",
    "networkLabel": "World Chain",
    "txHash": "0x...",
    "payer": "0xPayer...",
    "amountUsdc": 0.5,
    "amountAtomic": "500000"
  },
  "pricing": {
    "maxResponses": 100,
    "rewardPoolUsdc": 0.05,
    "qualiFeeUsdc": 0.45,
    "costUsdc": 0.5
  },
  "endpoints": {
    "poll": "https://getquali.com/api/v1/polls/<id>",
    "results": "https://getquali.com/api/v1/polls/<id>/results",
    "votes": "https://getquali.com/api/v1/polls/<id>/votes"
  }
}
GET/api/v1/polls/:idPublic
Public metadata for a settled API-created poll.
GET/api/v1/polls/:id/resultsX-Read-Token required
Aggregated counts and percentages.
bash
curl "https://getquali.com/api/v1/polls/<poll_id>/results" \
  -H "X-Read-Token: <your_read_token>"
json
{
  "id": "0c6b1e2d-...-3f9a",
  "question": "Which logo fits our sustainable coffee brand better?",
  "status": "active",
  "maxResponses": 100,
  "voteCount": 42,
  "complete": false,
  "expiresAt": "2099-01-15T12:00:00Z",
  "rewardPoolUsdc": 0.05,
  "options": [
    { "id": "...", "text": "Leafy minimal", "voteCount": 26, "percentage": 61.9 },
    { "id": "...", "text": "Bold coffee bean", "voteCount": 16, "percentage": 38.1 }
  ]
}
GET/api/v1/polls/:id/votesX-Read-Token required
Individual anonymised votes with declared demographics.
json
{
  "votes": [
    {
      "optionId": "...",
      "createdAt": "2099-01-15T09:14:02Z",
      "demographics": {
        "country": "GB",
        "yearOfBirth": 1994,
        "gender": "female",
        "employmentStatus": "full_time",
        "maritalStatus": null,
        "ethnicity": null,
        "householdIncome": "50k_75k"
      }
    }
  ],
  "nextCursor": "2099-01-15T09:14:02Z"
}
GET/api/v1/polls?payer=0x...Public
List settled API polls paid by a wallet address.

Webhooks

Provide callbackUrl to receive one best-effort poll.completed callback. URLs must be HTTPS and cannot point to localhost or private IP literals.

If QUALI_CALLBACK_SECRET is configured, verify X-Quali-Signature as sha256=HMAC(secret, deliveryId.body), where the signed string is X-Quali-Delivery + "." + rawBody.

json
{
  "event": "poll.completed",
  "pollId": "0c6b1e2d-...-3f9a",
  "status": "active",
  "voteCount": 100,
  "maxResponses": 100,
  "completedAt": "2099-01-15T13:04:11.000Z",
  "resultsUrl": "https://getquali.com/api/v1/polls/<id>/results",
  "votesUrl": "https://getquali.com/api/v1/polls/<id>/votes",
  "readToken": "a6e4...ef2"
}

Errors

StatusMeaning
400Malformed request or content moderation rejection.
401Missing read token.
402Payment required, invalid payment, or settlement failure.
403Read token does not match this poll.
404Poll not found, not API-created, or not settled.
409Payment signature was already used.
503Server missing payment or moderation configuration.

Built for agents

For LLM tool descriptions, fetch /agents.md. It is shorter, stable, and optimized for agent planning.

Suggested tool split

Register quali.quote, quali.create_poll, quali.results, and quali.votes. Only quali.create_poll needs wallet access.