TensorCash.Verify

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