Say, on-chain and in advance, where your bitcoin should go if someone else can move it.
You can move coins from an address that isn't yours. Before you do anything, find out whether its owner has already said where they want them sent.
Records published by the registry, newest first. Protected addresses are never listed — the chain stores only an 8-byte hash of them, so this shows what was published without revealing whose wallet it was. Destinations are shortened here; open a record to see it in full.
One published record, as it exists on the Bitcoin blockchain.
If coins were taken from an address you controlled, you can publish that here and get a page you can share. People who want to help send Bitcoin straight to you — it never passes through us.
Two things, both against the blockchain rather than your word for it: the drained address must really have received funds and really no longer hold them, and you must sign with the donation address to prove you control where the money is going. Neither can be talked around.
What is not checked is whether a theft happened — nothing can check that, and your page will say so plainly next to your story. It is published as a claim, and readers are told to treat it as one.
Sign this statement with your donation address — not the drained one. Losing the drained address's key is usually the whole problem, and this proves the thing that actually protects donors: that you control where the money is being asked to go.
Fill in both addresses above.
Three steps. Your coins never move, and you never give anyone a key or a seed phrase. If a step ever asks you for either, you are not on Bit ID.
No signing needed. Send the publishing fee from the address you entered above — that payment is what proves the address is yours.
Open your wallet's Sign Message tool, select the address you are protecting, and sign this exactly as it appears. Nearly every wallet has this — Electrum, Sparrow, Coldcard, Trezor, Bitcoin Core, BlueWallet, Nunchuk. It is not a transaction and it cannot spend anything.
Your signature is verified and is the only proof needed. This payment is a fee — it covers writing your record into a Bitcoin transaction, and it proves nothing on its own.
One HTTP GET, no key, no rate deal — and for this half of the service you can skip us entirely and read the chain yourself. If your agent moves other people's coins, this is the call it should make first.
curl https://bitid.online/api/lookup/bc1qexample…
{
"address": "bc1qexample…",
"key": "496eb0e29a50c9df",
"found": true,
"record": {
"rescue": "bc1qrescue…",
"bounty": 10,
"source": "self",
"txid": "…",
"height": 961204,
"confirmed": true
},
"revoked": false,
"pending": null
}
found: false means no record. If an incomplete array is
present, some transactions could not be read and the negative is not
conclusive — treat it as unknown, not as absent.
The registry is the blockchain. Point this at any Bitcoin node and it will answer without contacting bitid.online at all — which is the only version of this service worth trusting with an irreversible decision.
npx bitid-scan bc1qexample… --json
The full byte layout is at /api/spec, in
enough detail to reimplement the scanner in any language. It is 34 bytes.
The lookup is one HTTP GET with no auth, so wrapping it as a tool is a few lines. Give the model a tool along these lines:
{
"name": "bitid_lookup",
"description": "Check whether the owner of a Bitcoin address has published a rescue destination. Call this BEFORE moving funds from any address you did not expect to control.",
"input_schema": {
"type": "object",
"properties": {
"address": { "type": "string", "description": "The Bitcoin address in question" }
},
"required": ["address"]
}
}
Implementation: GET https://bitid.online/api/lookup/{address},
return the JSON verbatim. Agents should be told to treat
incomplete as "unknown" rather than "no record".
GET /api/marks returns claims that an address was robbed. They
are claims, not findings — we verify control of the donation address and
that the drained address lost its balance, never that a theft occurred,
because the chain cannot show that. Do not summarise one as a verified
theft. Every response carries verified and
unverifiable arrays; if you render a mark, render those too.
Publishing rather than reading? That is the other robot page — registering, watching and marking.
Publishing a record, watching for a sweep, and marking an address as hacked. Everything the Protect pages do, they do through these calls — and the first one you can do without us at all.
If your wallet can attach an OP_RETURN, you never need this
service. Publish the 34-byte record yourself in any transaction that spends
from the address you are protecting. Spending is the proof of
control, so a self-published record needs no signature, no fee to us, and
outranks anything we publish about the same address. See
/api/spec.
A paid alarm: a signed webhook when anything touches an address. We never receive the address and cannot work it out. You hash your own script and send a stub of the hash; every address whose hash starts the same way is in the same bucket, and we cannot tell which one is yours.
node bin/watch-prefix.mjs bc1qexample…
# prefix 59165
# bits 20
# bucket ~42 funded scripts share it — we cannot tell which is yours
curl -X POST https://bitid.online/api/watch \
-H 'content-type: application/json' \
-d '{"prefix":"59165","bits":20,"webhookUrl":"https://your.endpoint/hook"}'
When anything in your bucket moves you get one signed webhook for that block, listing the candidate scripts and amounts; you compare them with your own locally, and usually none of them is yours. That noise is the price of the privacy and it is billed like signal, because it costs the same to find: 50 sats/day plus 25 sats per notification, prepaid, roughly 1.1 false alarms a day at the default width — about a third of days are silent. Confirmed blocks only.
The rate is measured against our own node rather than estimated, and the
trade behind it is fixed: one false alarm per day buys you about 40 members
of crowd, at every width. A quieter webhook is a smaller crowd, always —
with one exception. Add "spendsOnly": true and you hear only
about coins leaving the bucket: half the noise for the same crowd,
about 0.55 alarms a day and 58% of days silent. It costs no privacy because
the filter is applied to the block rather than to your address. That is also
why there is no "only over 1 BTC" option — that one would tell us your
address holds more than 1 BTC. And if you are watching for a sweep, coins
arriving were never the emergency.
There is no endpoint that accepts an address — sending one gets an error
explaining what to send instead. Full derivation, prices and the width table:
/api/watch/info.
GET /api/mark/message?drained=&donation=&story= for the
exact text, signed with the donation address — a hack
victim usually cannot sign with the one that was emptied — then
POST /api/mark. Free. If the address has a paid rescue
registration we commit the mark on-chain at our expense; otherwise it is
published immediately and anyone can sponsor the timestamp. Donations go
straight to the victim; we never hold them.
Reading rather than publishing? That is the lookup page. The full endpoint list lives under Technicals.
Everything the website does, it does through the API below. There is no key to request and no rate deal to negotiate. This page is the index; the two task pages have the worked examples.
/agents.txt is the prose version of
this site for agents, /api/spec is the
on-chain byte layout in enough detail to reimplement the scanner, and
/.well-known/bitid.json
is the discovery card.
GET /api/lookup/{address} | the rescue record, if any |
GET /api/record/{key} | one record by its lookup key |
GET /api/record/{key}/matches?address= | does this address match that record? |
GET /api/validate?address= | is this an address we can encode? |
GET /api/message?protect=…&rescue=…&bounty=… | the exact text to sign |
POST /api/register | {protect, rescue, bounty, signature} |
POST /api/registration/{id}/sign | finish a failed spend proof |
POST /api/revoke | {protect, signature} |
GET /api/registration/{id} | progress of a registration |
GET /api/watch/info | the bucket watch, in full |
GET /api/watch/estimate?bits=&spendsOnly= | noise and crowd at one width |
POST /api/watch | {prefix, bits, webhookUrl} — never an address |
GET /api/mark/message?drained=&donation=&story= | the exact text to sign, donation key |
POST /api/mark | {drained, donation, story, signature} |
GET /api/mark/{key} | one mark, with its caveats |
GET /api/marks | live marks — claims, not findings |
POST /api/mark/withdraw | {drained, signature} |
GET /api/spec | the on-chain format |
GET /api/status | chain tip, fee, queue |
GET /api/records | every record we have published |
Keys leak. A seed ends up in a screenshot, a cloud backup, a bad hardware wallet, an old laptop. Often the person who finds it would rather return the coins than steal them — but they have no idea who you are, and you have no way to tell them. So the coins sit there until someone less scrupulous finds the same key.
A way to answer that question in advance, in the one place a stranger holding your key is guaranteed to be able to read: the Bitcoin blockchain. You publish a short record saying if you can move this address, send it here and keep this much. That is the whole idea.
It is a standing, public, cryptographically signed grant of permission from you. It is not an invitation to take anyone's coins, and it gives nobody any access they did not already have.
One OP_RETURN output, 34 bytes:
BID1 │ flags │ bounty │ key (8 bytes) │ rescue address (20–32 bytes)
The rescue address is stored in the clear, so anyone can read the registry in a block explorer. The protected address is stored only as an 8-byte hash — a rescuer already knows the address, so they can compute it, but nobody can trawl the chain for a list of wallets whose owners are expecting to be robbed.
We hold the address itself only while your registration is in flight: it is needed to check your signature and build the record, and it is erased — along with your signature and the signed statement, which give it back — the moment the record is published or the quote expires. A list of addresses whose owners expect to lose control of them is exactly what the hash above exists to prevent; it would be a poor trade to keep one here instead.
A record counts only if the transaction carrying it was entitled to publish it. There are two ways to be entitled, and both are checks on the transaction's inputs:
A self-published record beats ours. Sending coins to the registry address grants nothing — anyone can do that, so any scanner that reads outputs instead of inputs can be fed a forgery.