Quickstart: your first question in five minutes
Ask your first question from code over REST or MCP, in the free sandbox.
Last checked against the API.
On this page
Before you start
- A sandbox key. Keys start
pfy_test_and are issued by invitation during early access. Request one with your name, an email address, what you're building and roughly how many questions a week you expect. The key is shown once; keep it secret. - Scopes. A key can carry
questions:write,results:read,ratings:writeandreceipts:read. The examples below need all four. - Sandbox answers are synthetic. Test members answer within a few minutes, and every answer is marked
synthetic: true. Nothing reaches real people and nothing is charged.
REST in four calls
1. See what you can ask
export PFY_API_KEY=pfy_test_... # your sandbox key
curl https://pennyforyours.com/v1/question-types \
-H "Authorization: Bearer $PFY_API_KEY"2. Ask
Send an Idempotency-Key so a retry never asks twice. X-Agent-Name is optional; members see that the question came through an AI tool either way.
curl https://pennyforyours.com/v1/questions \
-H "Authorization: Bearer $PFY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-H "X-Agent-Name: my-agent" \
-d '{
"type": "quick_pick",
"prompt": "Which name sounds friendlier for a neighbourhood bakery?",
"options": ["Brightwell", "Corvane"],
"pool": 5
}'Response (shortened){
"id": "01M43ZQ8T5K2N7W0A9B4C6D8EF",
"state": "open",
"pool": 5,
"accepted": 0,
"counts": { "Brightwell": 0, "Corvane": 0 },
"answers": [],
"sandbox": true,
"cursor": 3,
"poll_after_seconds": 5
}3. Wait for answers
Pass the cursor you were given and wait up to 25 seconds. The call returns as soon as an answer, a state change or the summary arrives (changed: true), or when the wait runs out (changed: false). Repeat with the new cursor until state is closed.
curl "https://pennyforyours.com/v1/questions/$QUESTION_ID?cursor=3&wait_seconds=25" \
-H "Authorization: Bearer $PFY_API_KEY"Prefer a stream? GET /v1/questions/{id}/events sends Server-Sent Events for up to 50 seconds at a time and resumes with Last-Event-ID. See the API reference.
4. Rate the answers
Ratings feed each answerer's Standing. Then fetch a receipt with GET /v1/receipts/{answer_id} and verify it.
curl https://pennyforyours.com/v1/questions/$QUESTION_ID/ratings \
-H "Authorization: Bearer $PFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "ratings": [{ "answer_id": "$ANSWER_ID", "value": "up", "most_useful": true }], "final_choice": 1 }'To ask about an image, upload the raw bytes to POST /v1/uploads first (PNG, JPEG or WebP, up to 4 MB) and pass the returned upload_id as asset_id.
MCP in one line
In Claude Code:
claude mcp add --transport http pennyforyours https://pennyforyours.com/mcp \
--header "Authorization: Bearer $PFY_API_KEY"Then ask Claude something like "Ask five people which of these two names sounds friendlier". It will call ask_question, then get_results until the pool closes. Other clients, the tool list and the transport details are on the MCP page.
- Using Claude Desktop, claude.ai, Cursor, VS Code or OpenAI tools instead? Setup for each client.
- Using ChatGPT? Its connector screen can't send an API key yet; use a Custom GPT Action over REST.
Run the examples
The repository's examples/ folder has runnable Node scripts (Node 22 or later):
npm ci
export PFY_API_KEY=pfy_test_...
export PFY_BASE_URL=https://pennyforyours.com
node examples/node-rest.mjs # REST: quote, ask, long-poll, rate, verify a receipt
node examples/mcp-client.mjs # MCP: the official SDK client, same flow as tool calls
node examples/verify-receipt.mjs <jws> # check any receipt with the public keys, no key neededErrors
Errors are RFC 9457 problem documents. Branch on the last part of type; 429 responses carry Retry-After. Over MCP, the same problem comes back as a tool result with isError: true.
{
"type": "https://pennyforyours.com/problems/insufficient-scope",
"title": "Forbidden",
"status": 403,
"detail": "This key does not have the \"questions:write\" scope.",
"requestId": "6c1f..."
}| Status | Type | Meaning |
|---|---|---|
| 400 | validation-error | A field is missing or invalid; errors[] names it. |
| 401 | unauthenticated, keyless-not-allowed | No key, a bad key or a revoked key. x402 payment headers without a key are refused. |
| 403 | insufficient-scope | The key lacks the scope this call needs. |
| 404 | not-found | Unknown, or not yours. The two look the same on purpose. |
| 409 | invalid-state, quote-invalid | For example, cancelling after the first answer, or reusing a spent quote. |
| 422 | url-questions-disabled, screening-rejected | Links in a question, or content refused by screening. |
| 429 | rate-limited | Slow down; wait Retry-After seconds. |
Troubleshooting
| You see | Likely cause | Fix |
|---|---|---|
401 unauthenticated | No Authorization header, or Bearer missing | Send Authorization: Bearer pfy_test_… |
401 keyless-not-allowed | x402 payment headers without a key | Add your key; keyless calls are refused |
403 insufficient-scope | The key lacks a scope | Ask us for a key with the scopes you need |
422 url-questions-disabled | A link in the prompt | Upload a screenshot instead |
| No answers after 10 minutes in the sandbox | A large pool, or synthetic answering is slow | Use pools of 5 while testing; check state and accepted |
429 rate-limited | Too many calls | Wait Retry-After seconds; long-poll rather than loop tightly |
Next steps
- Verify a receipt yourself, offline, with our public keys.
- Limits and sandbox behaviour: pool sizes, rate limits and testing tips.
- Give an autonomous agent access safely.