How GoodHand ranks pros
Pros are ranked by jobs that were confirmed, not by stars. Every number on this page is read from the code that does the ranking.
No stars, no written reviews
There are no star ratings and no written reviews anywhere in GoodHand. A pro's standing is a count of their jobs, and each job counts for more the more proof it has.
The proof ladder
Every job climbs five rungs. A higher rung means more evidence that the job really happened and the work held up. Each rung has a weight; the weights are what the ranking adds up.
Two things subtract. If the customer answers no to the 30-day question, the job counts −0.6 (a failed hold). If the pro does not show up and it is reported as a no-show, that counts −1.
| Rung | What it means | Weight |
|---|---|---|
| claimed | The job exists: a customer accepted the pro's offer, or the pro listed a past job. Nothing has been checked yet. | 0.05 |
| photo | The pro took a before photo and an after photo, each signed by a registered phone. | 0.15 |
| on site | The pro scanned the customer's door code. The customer's phone shows a code that changes every 120 seconds, and the two phones had to be within 150 m of each other. | 0.4 |
| confirmed | The customer confirmed the job on their own phone and entered the price they paid. | 0.7 |
| held | 30 days after confirming, GoodHand asks the customer whether the work is still working. Yes makes the job held. Listed past jobs never reach this rung. | 1 |
| listed past job, client confirmed | A job the pro listed from before GoodHand, which the client then confirmed through a link sent to their email. Nobody checked it in person, so it counts 0.3: less than a job both phones proved on site. Imported past jobs never reach the held rung. | 0.3 |
Who confirms a job
Only the customer confirms. Four acts need a person on a phone, and an agent is refused (error agents_cannot_confirm): door_scan_start, finish_location, customer_confirm, hold_answer. A pro cannot confirm their own job, and the same phone or the same account on both sides of a job is refused.
The formula
Pros are ordered by one score, computed the same way for the website, the API and the MCP tools. Every input is returned with every search result, so anyone can recompute it.
There is no other input. In particular nothing in the score can be bought (see Costs).
- Jobs counted: the pro's jobs from the last 365 days, without cancelled jobs and no-shows. One customer counts for at most 3 jobs for the same pro.
- raw = (sum of the weights of the pro's jobs of the searched job type) + 0.5 × (the same for other job types of the same trade) − 0.6 × failed holds − 1 × no-shows
- n = jobs of the searched type + 0.5 × jobs of the same trade. shrunk = n / (n + 5) × raw + 5 / (n + 5) × city average. With few jobs the city average pulls the score to the middle, so one lucky job cannot put a new pro on top.
- score = shrunk × (0.6 + 0.4 × reply rate) × 1 / (1 + distance in km / 10). The reply rate is the share of requests older than 24 hours that the pro answered with an offer; a pro with none yet counts as 0.5. A pro 10 km away scores half of the same pro next door.
What stops a pro from faking clients
This is what the code does today, and what it does not do.
What exists
- On-site proof needs two different phones within 150 m of each other, with the customer's code that changes every 120 seconds. The same phone or account on both sides is refused.
- Before and after photos only count when a registered phone signed them.
- One customer lifts one pro by at most 3 jobs.
- A pro cannot confirm for a client. A listed past job is confirmed only through a link emailed to the client, and a pro's own sign-in email address is refused as a client. Such jobs reach confirmed at most, never held.
- A customer who says the work did not hold, or a no-show, subtracts from the score.
- A confirmation that comes from the pro's own phone or network (any address the pro used in the last 30 days) is recorded and shown to the pro, but it does not count.
- A pro can list at most 200 new past jobs in any rolling 24 hours, and 500 rows per request. Over that, the import is refused and says when to try again.
- If at least 10 listed past jobs have been answered and more than 1 in 10 of those clients said the job was not theirs, the pro's listed past jobs count for nothing until GoodHand has looked at it. Jobs done on site still count.
What does not exist yet
- No identity check on clients. For a listed past job, the pro types the client's name, month and price, and "confirmed" means only that whoever controls the typed email address clicked the link. Nothing stops a cousin, a friend or the pro from confirming; that address counts for at most 3 jobs, and a different address counts as a different client.
- No limit on how many accounts or phones one person can have. A pro with two phones and two accounts could pass the on-site check with themselves; only the same phone or the same account is refused.
- The price is what each side typed in. The confirmer types what they paid (left blank, it becomes the pro's own number), and it is not checked against a receipt or a payment.
- Reports go to GoodHand's operator in a daily summary, and nothing is removed automatically. Anyone can report a pro without signing in, and the pro is never told who. A client who never had a listed past job can say so from their link; that job then counts for nothing and the pro's page says how many clients did.
- Imports have no lifetime total, only pacing (200 new past jobs per pro in any rolling 24 hours, 500 rows per request): a pro can list any number of past jobs over time, and every customer counts equally, with no extra weight for a customer who has used GoodHand elsewhere.
- Held is the customer's own answer to "still working?". Nothing else checks it.
The same numbers for agents
Add ?format=json to this address, or send Accept: application/json, for every weight and constant above as data. The search API returns every score input per pro.
The numbers on this page are generated from the code at commit 8f5f3b6c76a0. Machine-readable version of this page (JSON)
Other languages: español · português · français · Deutsch · italiano · polski · українська · Türkçe · हिन्दी · Indonesia · 日本語 · 中文 · العربية · català · فارسی · shqip · հայերեն · Nederlands · svenska · azərbaycan · bosanski · বাংলা · български · Melayu · རྫོང་ཁ · беларуская · Ελληνικά · čeština · dansk · አማርኛ · eesti · suomi · føroyskt · ქართული · hrvatski · Haitian Creole · magyar · עברית · íslenska · кыргызча · ខ្មែរ · 한국어 · қазақ тілі · ລາວ · සිංහල · lietuvių · Lëtzebuergesch · latviešu · română · српски · македонски · မြန်မာ · монгол · Filipino · Malti · Divehi · Nyanja · norsk · नेपाली · اردو · русский · Ikinyarwanda · slovenščina · slovenčina · Soomaali · ไทย · тоҷикӣ · türkmen dili · lea fakatonga · Kiswahili · o‘zbek · Tiếng Việt · Bislama · Samoan · isiZulu