Quickstart: your first question in five minutes

By PennyForYours

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:write and receipts: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.

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 needed

Errors

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..."
}
Common problem types
StatusTypeMeaning
400validation-errorA field is missing or invalid; errors[] names it.
401unauthenticated, keyless-not-allowedNo key, a bad key or a revoked key. x402 payment headers without a key are refused.
403insufficient-scopeThe key lacks the scope this call needs.
404not-foundUnknown, or not yours. The two look the same on purpose.
409invalid-state, quote-invalidFor example, cancelling after the first answer, or reusing a spent quote.
422url-questions-disabled, screening-rejectedLinks in a question, or content refused by screening.
429rate-limitedSlow down; wait Retry-After seconds.

Troubleshooting

If something goes wrong
You seeLikely causeFix
401 unauthenticatedNo Authorization header, or Bearer missingSend Authorization: Bearer pfy_test_…
401 keyless-not-allowedx402 payment headers without a keyAdd your key; keyless calls are refused
403 insufficient-scopeThe key lacks a scopeAsk us for a key with the scopes you need
422 url-questions-disabledA link in the promptUpload a screenshot instead
No answers after 10 minutes in the sandboxA large pool, or synthetic answering is slowUse pools of 5 while testing; check state and accepted
429 rate-limitedToo many callsWait Retry-After seconds; long-poll rather than loop tightly

Next steps

Questions developers ask

Who do I contact about a problem?

Use the developer route on the contact page and include the requestId from the error response. A person reads every message.

How long does the quickstart take?

About five minutes once you have a key, plus a few minutes for synthetic answers to arrive.

Do I need Node?

Only for the example scripts (Node 22 or later). The four REST calls work with curl or any HTTP client.

Why do I need an Idempotency-Key?

So a retry never asks the same people twice. Use a fresh value for each new question and reuse it only when retrying that request.

How do I ask about an image?

Upload the raw bytes to POST /v1/uploads (PNG, JPEG or WebP, up to 4 MB) and pass the returned upload_id as asset_id. Over MCP, pass the image as base64.

Can I test with real people?

Not during early access. The sandbox uses synthetic members only, and live keys aren't issued yet.

Stuck?

Use the developer route on the contact page and include the requestId from the error response, so we can find the call. A real person replies, usually within 2 working days.

Contact the developer route