PluresDocs

Plures API

Send a decision to verified people before your agent acts on it. Each task goes to several people who answer independently. You get back the majority answer, how strongly they agreed, and every vote.

Quickstart

Request API access on the homepage and you’ll get a test key (pk_test_…) and a test webhook signing secret (whsec_…) right away. Tasks sent with a test key complete instantly and never reach real people, so you can build your whole integration before going live. When you’re ready for real verifiers, we set up your live key (pk_live_…) and live webhook secret after a short call.

curl https://plures.si/api/v1/tasks \
  -H "Authorization: Bearer $PLURES_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "question": "Is this customer asking for a refund?",
    "context": "Hi, the blender arrived cracked. I would like my money back please.",
    "options": ["Yes", "No", "Unclear"],
    "verifiers": 3,
    "webhook_url": "https://example.com/plures/webhook"
  }'

Prefer clicking first? The dashboard has a playground that sends real test tasks and shows the exact request and response.

The response is the task, with status: "pending". When enough people have answered, Plures sends a task.completed event to your webhook. You can also poll GET /api/v1/tasks/:id.

Create a task

Write the question so a careful person with no other context could answer it from what you show them. Keep personal data out of tasks unless you need it: people in many countries will see the content.

FieldTypeDescription
questionstring, requiredWhat people should decide. Up to 1,000 characters.
optionsarray, required2 to 8 choices. Strings, or objects {"id": "refund", "label": "Yes, a refund"} if you want stable ids. Results refer to options by id (a string option’s id is the string itself).
contextstringThe material to judge: a message, a listing, a reply. Shown as plain text with line breaks kept. Up to 20,000 characters.
image_urlhttps URLAn image to show with the task.
instructionsstringShort guidance shown under the question.
verifiersodd integer, 1 to 9How many people answer. Default 3.
max_verifiersinteger, up to 15If people split, Plures asks two more at a time until there’s a majority or this limit is reached. Default verifiers + 2.
presence_checkbooleanEvery person confirms it’s them with Face ID, a fingerprint or a passkey before their answer counts. Default true. Set it to false for low stakes tasks to skip the check on every answer; Plures still spot checks a share of them.
webhook_urlhttps URLWhere to send the result.
metadataobjectYour own data, returned with the task. Up to 4 KB.
expires_insecondsWhen to give up waiting. 60 to 604,800. Default 86,400 (one day).

Send an Idempotency-Key header to make retries safe. Repeating a request with the same key returns the original task instead of creating a new one.

Results

{
  "id": "tsk_8Jw2kQp4N7vRz1bXc5Td",
  "object": "task",
  "mode": "live",
  "status": "completed",
  "question": "Is this customer asking for a refund?",
  "options": [{ "id": "Yes", "label": "Yes" }, { "id": "No", "label": "No" }, { "id": "Unclear", "label": "Unclear" }],
  "verifiers": 3,
  "result": {
    "answer": "Yes",
    "label": "Yes",
    "agreement": 1,
    "answers": 3,
    "votes": { "Yes": 3, "No": 0, "Unclear": 0 }
  },
  "metadata": { "ticket": "48213" },
  "created_at": "2026-10-16T14:02:11.000Z",
  "completed_at": "2026-10-16T14:03:40.000Z",
  "expires_at": "2026-10-17T14:02:11.000Z"
}
  • answer is the option id most people chose. It’s null if people stayed tied after max_verifiers answers (then tie is true), or if the task expired.
  • agreement is the share of people who chose that answer, from 0 to 1. If you set up your team, Plures sends anything below your threshold to them and you only ever get a final answer.
  • decided_by is "verifiers" or "team". A team decision also has reviewer (their email) and an optional note, and keeps the verifiers’ votes for the record.
  • Statuses: pending, completed, expired (not enough answers in time; votes holds what came in), and canceled.

Your team

Add the people who should make the call when verified people don’t all agree, in your dashboard. From then on, a split, a tie or a task nobody finished in time goes to them instead of back to your agent: Plures emails each of them a personal link to a page with the question, what your agent sent and how verified people voted, and the first one to decide answers it. The task stays pending until then, so your agent gets one final answer and your code doesn’t change.

"result": {
  "answer": "No",
  "label": "No",
  "decided_by": "team",
  "reviewer": "ops-lead@yourcompany.com",
  "note": "No photo of the damage, so we ask for one first.",
  "agreement": 0.667,
  "answers": 3,
  "votes": { "Yes": 2, "No": 1 }
}
  • By default anything short of everyone agreeing goes to your team. In the dashboard you can choose below 80%, below 60%, any percentage you set, or only when there’s no answer.
  • Per task, send escalate: false to always get the verifiers’ answer back, or escalate_below (0 to 1) to set the threshold for that task.
  • While it’s with your team the task has an escalation object with the reason (split, tie or no_answer), the verifiers’ result and a team_deadline. Your team has 24 hours; after that the task expires.
  • Test tasks go to your team too, marked as tests. Use metadata.test_outcome set to split to try the whole flow.

Ledger

Every finished decision is added to the Plures ledger, a tamper evident record where each entry carries the SHA-256 fingerprint of the one before it. Test and Live are separate chains. The public ledger shows only fingerprints and outcomes; your questions and answers stay private. Each task gets a ledger object with its entry number and a public receipt link once it’s recorded.

  • GET /api/v1/ledger lists your entries, newest first, with the question, answer and the content_salt and content that let you check each content_digest: SHA-256 of `${content_salt}|${content}`.
  • An entry’s hash is SHA-256 of plures-ledger-v1|mode|seq|task_id|decided_at|status|decided_by|reason|agreement|answers|confirmed|content_digest|prev_hash, with empty strings for missing values.
  • Public JSON: GET /api/ledger?mode=live and GET /api/ledger/live/{seq}. Export everything as CSV or JSON from your dashboard.

Webhooks

Plures sends a POST to your webhook_url when a task is task.completed, task.expired or task.canceled. Respond with any 2xx status. Failed deliveries are retried after 1, 5, 30, 120, 720 and 1,440 minutes.

{ "id": "evt_…", "type": "task.completed", "created_at": "…", "data": { /* the task */ } }

Every delivery has a Plures-Signature header: t=<unix seconds>,v1=<hex>, where v1 is HMAC SHA256 of `${t}.${rawBody}` with your webhook secret. Check it before trusting the event. The SDKs do this for you:

import { Plures } from "./plures";

export async function POST(req: Request) {
  const raw = await req.text();
  const event = await Plures.verifyWebhook(raw, req.headers.get("plures-signature"), process.env.PLURES_WEBHOOK_SECRET!);
  if (event.type === "task.completed" && event.data.result?.answer === "Yes") {
    // let your agent issue the refund
  }
  return new Response("ok");
}

SDKs

Official clients for JavaScript and Python. Both have no dependencies, read PLURES_API_KEY from the environment, retry server errors and verify webhook signatures. Source and examples for LangGraph, the OpenAI Agents SDK and the Vercel AI SDK are on GitHub.

npm install plures
import { Plures } from "plures";
const plures = new Plures(); // reads PLURES_API_KEY

// Create and wait (handy in scripts and agent tools; use webhooks in production)
const task = await plures.verify({
  question: "Does this reply answer the customer's question?",
  context: "Q: Can I change my delivery address?\nA: Our store hours are 9 to 5.",
  options: ["Yes", "No"],
});
if (task.result?.answer === "No") { /* don't send the reply */ }

Prefer a single file? Download plures.ts or plures.py into your project instead.

MCP server

Give any agent that speaks the Model Context Protocol a human in the loop. The plures-mcp server adds an ask_humans tool to Claude, Cursor, VS Code and agent frameworks with MCP support. The agent sends the judgment call and gets back the verified answer and how strongly people agreed, with guidance to act only on unanimous answers.

{
  "mcpServers": {
    "plures": {
      "command": "npx",
      "args": ["-y", "plures-mcp"],
      "env": { "PLURES_API_KEY": "pk_test_..." }
    }
  }
}

Tools: ask_humans (question, options, context, image_url, instructions, verifiers, wait_seconds up to 240), get_human_answer (task_id) and cancel_human_task. If people haven’t answered within wait_seconds, the agent gets a task id to check later. With a test key the answer is simulated and instant.

Reference

Base URL https://plures.si/api/v1. Authenticate with Authorization: Bearer <key>. Requests and responses are JSON.

EndpointDescription
POST /tasksCreate a task. Returns 201 with the task.
GET /tasks/:idRetrieve a task.
GET /tasksList tasks for the key’s mode, newest first. Query: status, limit (1 to 100), starting_after (a task id, for paging).
POST /tasks/:id/cancelCancel a pending task. Answers already given are still billed.

Errors

Errors use standard HTTP status codes and a JSON body: {"error": {"type": "invalid_request", "message": "options must be an array of 2 to 8 choices."}}

StatusTypeMeaning
400invalid_requestSomething in the request is missing or wrong. The message says what.
401authentication_errorThe API key is missing, wrong or revoked.
404not_foundNo task with that id for this key.
500api_errorA problem on our side. Retry with the same Idempotency-Key.

Test mode

Tasks created with a pk_test_ key complete immediately without reaching anyone, and still fire your webhook, signed with your test webhook secret. The answer is the first option, or set metadata.test_answer to an option id to choose it. Test and live tasks are listed separately. Test keys can create up to 1,000 tasks a day.

Set metadata.test_outcome to try every path your code handles, including the ones you send to your own team:

  • unanimous (the default): everyone agreed, agreement 1.
  • split: a majority but not everyone, for example 2 of 3 and agreement 0.667. The usual case to route to a person on your team.
  • tie: people stayed split after more were asked. answer is null and tie is true.
  • expired: not enough answers in time. Status expired, answer null, and a task.expired webhook.
  • slow: stays pending for about 5 seconds, then completes, so you can test polling and late webhooks.
const task = await plures.tasks.create({
  question: "Should this customer get a refund?",
  options: ["Yes", "No"],
  context: ticketText,
  webhook_url: "https://your.app/plures",
  metadata: { test_outcome: "split" }, // test keys only; ignored by live keys
});

Questions or a use case to discuss? Email hello@plures.si. Use of the API is subject to the Terms of Service, including the Developer Terms in Section 6.