Draft. Not legal advice. Written by the developer who wrote the enforcement code, not by a lawyer. The rules below are deliberately written to match what the code actually does — no more. Where a rule is honour-based rather than enforced, it says so. Have a qualified lawyer review this before it is published.
Version 0.1 · draft of 25 August 2026 · forms part of TERMS.md.
1. The one rule
Every measurement you submit must be one you actually observed.
Everything else in this document follows from that. The only asset batchwatch has is that the numbers are true. A service that publishes crowdsourced percentiles has exactly one way to fail permanently, and this is it.
2. Prohibited
You must not:
- Submit a measurement of a job that did not happen. No synthetic rows, no test data left pointed at production, no "representative" figures, no timings copied from somewhere else.
- Misreport what did happen. Do not submit timestamps that are not the real start and end of the job. Do not relabel the model, the provider, the mode (
batchvssync) or the token counts. - Submit measurements you did not take. Do not forward another party's timings as your own; each key is meant to be one source.
- Run multiple keys in order to gain influence over the published figures. One organisation, one voice. Creating additional keys to get additional votes is the specific attack this service is built against, and it is prohibited whether or not it currently succeeds.
- Deliberately evade the free-trial counter by changing IP address, cycling keys, or otherwise resetting the count in order to keep using the gated routes without contributing. The trial is explicitly not a security boundary; going around it anyway is a breach of this policy.
- Exceed the key-creation limit by rotating addresses. The limit of 5 keys per IP per day is hygiene against a runaway loop, not a wall. Do not treat it as a challenge; one key per service is plenty, and keys are not per-machine.
- Attack the availability or integrity of the service — flooding ingest, attempting to drive a model's confidence to
very_lowto make it useless to others, exploiting a vulnerability rather than reporting it. - Scrape the API in order to reconstruct and redistribute the aggregate dataset. Reading the data for your own use, including commercially, is the point. Republishing it as a competing dataset is not licensed. See TERMS.md §7.
- Put personal data in the free-text fields —
model,endpointand the keylabel. They are stored verbatim and not scrubbed. See PRIVACY.md §2.
3. What the code actually checks
This section exists so nobody is surprised in either direction — neither by a rule enforced more tightly than expected, nor by a defence that turns out not to exist.
The live path is checked against our clock
POST /v1/calls → PATCH /v1/calls/{id} is open to everyone, with or without a key, because it is self-limiting:
- The start time is our server timestamp. Yours is stored as advisory only.
- On finish, you may state when the job ended — but the value is accepted only if it falls between our recorded start and now. Anything else is discarded and the arrival time is used instead.
- The difference between your clock and ours is stored on the row, and if it exceeds 300 seconds the response tells you so.
- Duration is always
ended_server − started_server. You cannot submit a duration. That would make the dataset a collection of claims.
The consequence is that faking an eight-hour wait on this path costs eight hours of real waiting. That is why it needs no key.
The import path is not, so it needs a key
POST /v1/calls/complete takes your timestamps, because it exists to import history. There is no physics left in it. It checks only that both timestamps are present, that the end is not before the start, that the provider and model are valid, and that a request carries at most 500 records.
It therefore requires an API key. This is not a formality: measured against a running worker before the route was closed, 2,000 fabricated eight-hour jobs took four anonymous requests and 287 ms, moved a model's p50 from 146 s to 28,800 s, and flipped the verdict. A key does not prevent that, but it makes it named, revocable and visible.
Percentiles are decided by contributors, not by volume
This is the defence that actually works, and it is worth stating precisely because the rule above ("one organisation, one voice") is the human version of it.
- Each source's own percentile is computed first. The published number is the median of those figures, not a percentile of the pooled rows.
- A source only gets a vote if it has a key, at least 5 measurements, and measurements on at least 3 separate days. Time is the one input an attacker cannot hurry.
- Everything that has not earned a vote shares a single vote — fresh keys and all anonymous rows together. Ten new keys therefore count for exactly as much as one.
- All keyless rows count as one source, because we cannot tell them apart and will not pretend we can.
- Below 3 voting contributors there is no robustness to be had. The response says so —
robustness.method: "pooled"— and confidence is capped accordingly. - With an even number of votes we take the upper of the two middle values, not the average. Pulling a number down is the dangerous direction: a too-low figure says "batch will make it" and blows a deadline. Erring high costs money, not deliveries.
Separately, each source is capped at half of what all other sources contribute combined before the pooled figures are computed, sampled evenly in sorted order so the shape — and the tail — survives the cut.
Measured effect: a keyed attacker submitting 5,000 fabricated rows against three honest contributors moved p50 and p90 by zero seconds, and dropped confidence from high to very_low. The system says it is unsure rather than saying something wrong.
4. What these defences do not do
Published here rather than hidden, because a defence you overstate is worse than one you do not have.
- A patient attacker with several accounts can still reach a majority. Four accounts run for a week, producing plausible-looking data on separate days, can out-vote three honest contributors. There is a test in the repository that says so out loud. Quarantining outliers until a second established contributor corroborates them is the next step, and it is not built yet.
- Confidence intervals are computed on the pooled data on purpose, so an attacker can widen them upward. That makes us recommend synchronous calls, which costs money, not deadlines. It is the safe direction and it is deliberate.
- Denial of service on a model's usefulness is possible. Driving confidence to
very_lowfor a model makes the answer useless without making it wrong. It is not free, but it is not prevented. size_effectcompares the size band against a non-capped pool, so that particular flag can be flipped by volume. Known, recorded, not yet fixed.- The free trial is not a security boundary and is not built as one. Anyone can change IP address. The data is aggregate percentiles, not secrets; what makes the product work is that sustained use requires contributing.
- There is no automated abuse detection. Nothing scans for suspicious submissions. Enforcement under §6 is a human noticing.
5. Our own measurements
Our prober submits real batch jobs to real queues on our own provider accounts and records them like any other measurement, marked source: 'probe'. They count in the statistics because they are real jobs in the real queue. /v1/coverage reports crowdsourced: false while our own jobs are all we have for a model — a dataset that is really one account has to say so.
Where the prober gives up on a job it records abandoned, never completed. We stopped looking; the job did not expire; the true wait is unknown and longer than what we saw. Only completed rows enter any percentile.
6. Enforcement
If we conclude this policy has been breached we may:
- Revoke the key, so it stops authenticating.
- Exclude the key's history from every published figure by setting the
excludedflag. Because every published number is computed at request time and filters excluded rows, this takes effect on the next request — there is no rebuild to wait for. - Delete the rows.
- Block access to the service.
Note honestly: there is no admin route for any of this. Revocation, exclusion and deletion are manual database operations by the operator. There is no queue of reports, no automated scoring, and no appeal process beyond writing to hello@batchwatch.dev. Where we can say why we acted, we will.
7. Reporting
- A vulnerability, or a way to move the published numbers: please tell us at hello@batchwatch.dev before telling anyone else. Finding a hole in the vote model is genuinely useful and will be credited if you want it to be. Do not test it against production at a scale that pollutes the dataset — say what you found and we will reproduce it ourselves.
- Data you believe is fabricated: hello@batchwatch.dev, with the provider, model and rough window.