Guide
Verifying shares
A share is a proof that meets your pool's target but not the block's. The service verifies it exactly like a block, except that the proof-of-work check uses the share target you send in a header. Everything else (the proof, the model replay, the chain rules for the candidate's height) is checked in full.
Before you start
Share verification needs the verify:share-mode scope on your key. Portal
keys do not carry it by default: ask for it through the contact address on the
portal, naming the key. It is granted per key and takes effect within 30
seconds. Until then a request that carries the share target header is
answered 403.
What to send
The body is the ValidationRequest FlatBuffer a TensorCash node builds for
the candidate block: a BlockValidation with the header fields, the proof,
and the candidate's height and ruleset. Build it for the share exactly as
you would for a block candidate; do not change anything to make it "a share".
The header X-Target-Override carries your share target as 32 bytes of
hex (64 characters, no 0x). The share passes the proof-of-work check when
the candidate's header hash is at or below this value.
Send both to the Full submit route. Share requests use the plain reply format:
do not add X-Verify-Result-Contract (the service answers 400 if you do).
curl -sS https://api.verify.tensorcash.boblabs.eu/v1/verify/full/request/submit \
-H "Authorization: Bearer $TVK" \
-H "Content-Type: application/octet-stream" \
-H "X-Target-Override: 00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" \
--data-binary @share.bin
Submit, poll, result
Submit. Three answers are possible:
| Reply | Meaning |
|---|---|
202 {"status": "pending", "hash_id": "…", "verification_type": "full", "state": "accepted"} |
Queued. Poll. |
200 with a verdict |
Already known, or the share missed the target (see below). Done. |
4xx / 429 |
Nothing was started. See Limits and errors. |
A share whose header hash is above your target is answered at once, without GPU work:
{"status": "Full_Red", "hash_id": "…", "target_override_applied": true,
"target_override_met": false, "computed_header_hash": "0000a3…"}
Poll the Full status route for the share's hash_id, with the same
X-Target-Override header. Without the header the lookup only sees
block-target results and answers 404.
curl -sS https://api.verify.tensorcash.boblabs.eu/v1/verify/status/full/$HASH_ID \
-H "Authorization: Bearer $TVK" \
-H "X-Target-Override: $SHARE_TARGET"
202 with "state": "queued" or "running" means wait; poll every 10 seconds
or so. Polling is free.
Result. 200 with the verdict:
{
"status": "Full_Green",
"hash_id": "6f1c…e2a9",
"pow_blob_hash": "a3…17",
"elapsed_ms": 10400,
"cached": true,
"target_override_applied": true,
"computed_header_hash": "000003f1…",
"assurance": "unsampled"
}
computed_header_hash is the candidate's chain header hash, the number you
compare against your target for share weight. target_override_applied: true
confirms the result was computed under your share target.
What to do with each status
| Status | What it says | Pool action |
|---|---|---|
Full_Green |
The proof is valid under the share target: the model ran, the chain rules hold. | Pay the share. |
Full_Red |
The proof failed a check, or missed the share target (target_override_met: false). |
Reject the share. |
Full_Amber |
The check completed without a decision either way; the reply carries details. |
Your policy: hold, re-check, or treat as unpaid. |
pending |
Not finished. | Poll again. |
The full list, including the Quick-Smell strings, is in Statuses.
Polling many shares
The batch status route (POST /v1/verify/status/batch) answers block-target
results only; it has no field for the share target. Poll shares one at a time
on GET /v1/verify/status/full/{hash_id} with the header. Status polls are not
counted against your submit limit and may run up to 50 at a time per key, so a
pool can keep a few hundred shares in flight without trouble.
A cheaper lane exists for high volume: the Quick-Smell submit route
(/v1/verify/quick-smell/request/submit) takes the same body and header, runs
on CPU in seconds, and is polled on GET /v1/verify/status/quick-smell/{hash_id}
with the header. It checks the proof-of-work and the token statistics but does
not replay the model; its verdicts are Quick_OK_Smell_OK (pass),
Quick_OK_Smell_Fail, Quick_Fail_Smell_OK and Quick_Fail_Smell_Fail
(reject). GET /v1/verifier/capabilities says whether Quick_Smell is being
served at the moment.
A worked loop
Python, requests. Submits a batch of shares, then polls until every one has a
verdict, honouring Retry-After on 429.
import time, requests
API = "https://api.verify.tensorcash.boblabs.eu"
H = {"Authorization": "Bearer " + TVK}
def submit(share_bytes, share_target_hex):
r = requests.post(f"{API}/v1/verify/full/request/submit", data=share_bytes,
headers={**H, "Content-Type": "application/octet-stream",
"X-Target-Override": share_target_hex})
while r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", "10")))
r = requests.post(f"{API}/v1/verify/full/request/submit", data=share_bytes,
headers={**H, "Content-Type": "application/octet-stream",
"X-Target-Override": share_target_hex})
r.raise_for_status() # 4xx other than 429: fix the request
return r.json() # 200 -> final, 202 -> pending
def poll(hash_id, share_target_hex):
r = requests.get(f"{API}/v1/verify/status/full/{hash_id}",
headers={**H, "X-Target-Override": share_target_hex})
if r.status_code == 202:
return None
r.raise_for_status()
return r.json()
def verify_shares(shares): # [(share_bytes, share_target_hex), ...]
verdicts, pending = {}, {}
for body, target in shares:
reply = submit(body, target)
if reply["status"] == "pending":
pending[reply["hash_id"]] = target
else:
verdicts[reply["hash_id"]] = reply
while pending:
time.sleep(10)
for hash_id, target in list(pending.items()):
reply = poll(hash_id, target)
if reply is not None:
verdicts[hash_id] = reply
del pending[hash_id]
return verdicts
for hash_id, v in verify_shares(my_shares).items():
if v["status"] == "Full_Green":
pay(hash_id, weight_from(v["computed_header_hash"]))
elif v["status"] == "Full_Red":
reject(hash_id)
else: # Full_Amber
hold(hash_id, v.get("details"))
A share you submit twice is not verified twice: the second submit answers
202 while the first is running and 200 once it is done.
Throughput
Each Full verification takes about 10 seconds of GPU time. Per key you can
submit 6 Fulls per minute (burst 3) with 2 submits in flight at once;
a submit answered from an existing result ("cached": true) is not counted.
That is roughly 360 Full-verified shares per hour per key. If your pool needs
more, ask us through the portal's contact address.
Quick-Smell submits are limited to 60 per minute per key (burst 20), status polls to 300 per minute (burst 60). The tables are in Limits and errors.
Updated 2026-10-07