What it is

A scientific-grade random sampler built on /api/ints (for index-based selection from a numbered frame) and /api/shuffle (for permutation-based selection without replacement). The seed and the algorithm are public, so any reviewer can replay the draw months or years later and confirm that the published sample matches the published methodology.

A sample and its proof

This is what anyone can check. The three values in the steps are all a verifier needs, and the replay button recomputes the draw right here, without calling Provable.

GET /api/ints?clientSeed=trial_acme_phase2_sample_2026_05_25&count=10&min=1&max=5000Example record
{
  "clientSeed": "trial_acme_phase2_sample_2026_05_25",
  "nonce": 0,
  "serverHash": "949990c4ce02b9308ccf9b41d0a6d15b976d51d26f8d39cec86562c617962d40",
  "outcome": [1071, 404, 1218, 975, 2280, 1420, 2802, 1039, 556, 2650]
}
  1. Before the draw, the server commits to a seedPublish this hash before the draw, so the seed can't be swapped afterwards.serverHash = sha256(serverSeed) = 949990c4ce02b9308ccf9b41d0a6d15b976d51d26f8d39cec86562c617962d40
  2. The draw is a pure function of both seedsHMAC-SHA256(serverSeed, "trial_acme_phase2_sample_2026_05_25:0:0") → 10 integers in 1–5000 = [1071, 404, 1218, 975, 2280, 1420, 2802, 1039, 556, 2650]
  3. After the draw, the seed is revealed and anyone can replay itserverSeed = 18038732cf04a11ac05c15f119e25c2df7333f018d2e248d06bdf932de2507df

Try it live — draw a sample of 10 from a frame of 5,000

curl "https://api.provable.io/api/ints?clientSeed=trial_acme_phase2_sample_2026_05_25&count=10&min=1&max=5000"

For sampling without replacement, shuffle the numbered frame and take the first N — equivalent to a Fisher-Yates simple random sample:

curl "https://api.provable.io/api/shuffle?clientSeed=audit_invoices_q2_2026_srs&items=INV-001,INV-002,...,INV-015"

Integration snippet

// Stratified random sample: draw n_h units from each stratum independently,
// each with its own seed for separable replay.
async function drawStratifiedSample({ studyId, strata }) {
  const out = {};
  for (const [stratum, { frame, n }] of Object.entries(strata)) {
    const url = new URL("https://api.provable.io/api/shuffle");
    url.searchParams.set("clientSeed", `${studyId}:${stratum}`);
    url.searchParams.set("items", frame.join(","));

    const res = await fetch(url, {
      headers: { "x-api-key": process.env.PROVABLE_KEY }
    });
    const { outcome, serverHash } = await res.json();
    out[stratum] = { selected: outcome.slice(0, n), serverHash };
  }
  return out;
}

const sample = await drawStratifiedSample({
  studyId: "trial_acme_phase2",
  strata: {
    site_us: { frame: usParticipantIds,  n: 50 },
    site_eu: { frame: euParticipantIds,  n: 30 },
    site_jp: { frame: jpParticipantIds,  n: 20 },
  },
});
// Publish: each stratum's serverHash, the published frame, the script.

Why this is reproducible

Sampling designs that fit

Where it fits

Related