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
Get a quote
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.
Create a V2 payment signature
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 };
}Create the poll
# 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 size | Agent pays | Voter pool | Quali 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.
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, USDC0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913. - World Chain:
eip155:480, USDC0x79A02482A880bCE3F13e09Da970dC34db4CD24d1. - Base Sepolia and World Chain Sepolia can be enabled for staging.
Endpoints
Base URL: https://getquali.com/api/v1
/api/v1/quotePublicpaymentRequired object.maxResponses: integer >= 100.network: optional;baseorworldchain.
/api/v1/pollsx402 payment required{
"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"
}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"
}
}/api/v1/polls/:idPublic/api/v1/polls/:id/resultsX-Read-Token requiredcurl "https://getquali.com/api/v1/polls/<poll_id>/results" \
-H "X-Read-Token: <your_read_token>"{
"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 }
]
}/api/v1/polls/:id/votesX-Read-Token required{
"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"
}/api/v1/polls?payer=0x...PublicWebhooks
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.
{
"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
| Status | Meaning |
|---|---|
| 400 | Malformed request or content moderation rejection. |
| 401 | Missing read token. |
| 402 | Payment required, invalid payment, or settlement failure. |
| 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. |
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.