BEACONPet registry
Engineering documentation · BEACON/1 draft-1

Technicals

Everything you need to write, read, sign, broadcast and index BEACON records without this website. There is no API key, no registration, no partnership, no fee to us and no step where we have to agree. If you inscribe a valid record with your own tools and your own wallet, it is a real BEACON record the moment it confirms — and this resolver will index it exactly like one of our own.

Raw specification (Markdown) Agent memory file (for AI) Vocabulary (JSON) Human-readable spec

1 · Read a record in 40 lines

Start here, because it proves the rest. A BEACON record is a plain-text inscription on Dogecoin. If you can fetch a transaction from any Dogecoin explorer with a transaction API, you can read every record ever written, with no library, no SDK and nothing from us.

#!/usr/bin/env python3
"""Read a BEACON record from a Dogecoin reveal txid. No dependencies."""
import json, sys, urllib.request

BOOK = "https://dogebook.example/api/v2"          # any Blockbook instance
def tx(t): return json.load(urllib.request.urlopen(BOOK + "/tx/" + t))

def chunks(hexstr):                                # parse script -> push chunks
    b, out, i = bytes.fromhex(hexstr or ""), [], 0
    while i < len(b):
        op = b[i]; i += 1
        if op == 0x00:            out.append((op, b""))
        elif op < 0x4c:           out.append((op, b[i:i+op])); i += op
        elif op in (0x4c,0x4d,0x4e):
            w  = {0x4c:1, 0x4d:2, 0x4e:4}[op]
            n  = int.from_bytes(b[i:i+w], "little"); i += w
            out.append((op, b[i:i+n])); i += n
        else:                     out.append((op, None))
    return out

def number(c):                                     # numberToChunk(), inverted
    op, data = c
    if op == 0x00: return 0
    if 0x51 <= op <= 0x60: return op - 0x50        # OP_1..OP_16
    return int.from_bytes(data, "little")

def inscription(reveal):                           # walk the P2SH reveal chain
    per_tx, txid = [], reveal
    while True:
        t  = tx(txid)
        cs = chunks(t["vin"][0].get("hex"))[:-2]   # drop sig + redeemScript
        if not cs: raise SystemExit("no ord envelope")
        per_tx.append(cs)
        if cs[0][1] == b"ord": break               # reached the marker
        txid = t["vin"][0]["txid"]
    cs     = [c for part in reversed(per_tx) for c in part]
    nparts = number(cs[1])
    ctype  = cs[2][1].decode()
    data, i = bytearray(), 3
    for _ in range(nparts):
        i += 1; data += cs[i][1] or b""; i += 1    # skip parts-remaining
    return ctype, bytes(data)

ctype, payload = inscription(sys.argv[1].lower())
assert ctype.startswith("text/plain")
text = payload.decode("utf-8")
assert text.splitlines()[0].strip() == "BEACON/1"
print(text)
That is the entire read path. Our own resolver (chain.py, ~300 lines) is this same algorithm with caching, error handling and custody tracing bolted on. Nothing in it is privileged.

2 · The record grammar

Formally, in ABNF (RFC 5234), over UTF-8 octets:

record       = magic-line 1*( LF line )
magic-line   = %s"BEACON/1"                 ; exact, case-sensitive, no BOM
line         = blank / comment / field / junk
blank        = *WSP
comment      = *WSP "#" *VCHAR
field        = *WSP key ":" value *WSP
key          = 1*8( %x41-5A / %x30-39 )     ; [A-Z0-9]{1,8}
value        = *( %x20-10FFFF )             ; may itself contain ":"
junk         = *VCHAR                       ; no colon, or bad key -> IGNORED

Six rules make this survivable, and they are the whole reason the format is this boring:

trailing CRstripped — a record written on Windows parses identically
leading/trailing WSPstripped on every line
first colon winsID:ISO11784:985141001234567 → key ID, value ISO11784:985141001234567
whitespace around the key and the valuestripped, so N: Biscuit, N:Biscuit and N : Biscuit are the same. A value can never begin or end with whitespace
duplicate keysfirst occurrence wins. Truncation at the end is therefore safe
unknown keyspreserved and ignored — never an error. This is what makes BEACON/1 extensible without a version bump

Keys

KeyGenesisMeaningHard limit
TrequiredProfile token: PET or ASSET in draft-1[A-Z0-9]{2,16}
IDrequiredSCHEME:VALUE — what the beacon is aboutvalue ≤ 128
NrequiredDisplay name≤ 64
1–6optionalThe six information lines; the profile fixes their meaning≤ 200 each
SoptionalStatus token; default ACTIVEtoken
AoptionalDelegated authority — a second Dogecoin address allowed to updateP2PKH address
UoptionalA URL for more information≤ 200
P—Presence of P makes it an update, not a genesis. Value is the genesis txid, 64 lowercase hex64 hex
X C Mreservedcross-reference / certificate txid / key=value; extension list—

The machine-readable copy of the profiles, statuses, identifier schemes and every limit above is served as JSON at /api/profiles so a client never has to hard-code them.

Identifier normalisation — the one subtle rule

Records are stored exactly as written. Normalisation applies only when matching two identifiers:

def id_key(raw):                        # matches the reference implementation
    scheme, _, value = raw.partition(":")
    scheme = scheme.strip().upper()
    value  = value.strip()
    for ch in " -.:":  value = value.replace(ch, "")  # separators are noise
    return scheme + ":" + value.upper()               # (colons included)

id_key("iso11784:985 141-001.234567") == id_key("ISO11784:985141001234567")   # True
Known discrepancy: the ISO11785 alias is not folded. §5 of the specification lists ISO11785 as an alias of ISO11784, but the reference id_key() above does not collapse it — so a record written ID:ISO11785:<15 digits> will not be found by a lookup for ISO11784:<same digits>. Verified against the running parser on 2026-08-14. Until this is resolved, write ISO11784, and if you are building an indexer, decide deliberately whether to fold the alias and say which you did. Interoperating with this resolver today means matching the code, not the prose.

An unregistered scheme is not an error. A reader MUST accept it and treat the identifier as opaque. Scheme validation exists to warn a writer about a typo before they spend money; it is never a reason to refuse to read the chain. An indexer that drops records it does not recognise has broken rule 4.

3 · The Dogecoin envelope, byte by byte

BEACON deliberately invents nothing at the chain layer. It uses the ordinary ord-style doginal envelope, unchanged, so every existing Dogecoin inscription indexer already sees BEACON records without a line of new code. If you already have a doginals inscriber, you already have a BEACON writer.

3.1 The chunk sequence

The payload is encoded as a flat sequence of script chunks:

push("ord")                 ; the 3-byte ASCII marker
push(nParts)                ; number chunk
push(contentType)           ; "text/plain;charset=utf-8" as ASCII
for n in 0 .. nParts-1:
    push(nParts - n - 1)    ; number chunk: parts REMAINING after this one
    push(parts[n])          ; ≤ 240 data bytes

3.2 Push encoding — get this wrong and the record is unreadable forever

len ≤ 75single opcode byte = the length, then the data
76 ≤ len ≤ 2550x4c (OP_PUSHDATA1), 1 length byte, data
256 ≤ len ≤ 655350x4d (OP_PUSHDATA2), 2 length bytes LE, data
n = 00x00 (OP_0)
1 ≤ n ≤ 160x50 + n (OP_1 … OP_16)
n > 16little-endian magnitude, sign-padded: append an extra 0x00 whenever the top byte has its MSB set
The sign-padding trap. ord-dogecoin's push_data_to_number() reads number chunks as unsigned little-endian. A signed CScriptNum decoder reads the raw two bytes [0x00, 0x80] as -0, i.e. zero. Emitting naïve 2-byte LE for values in 32768–65535 would index correctly on ord today and silently destroy the inscription under any signed reader — on a chain you cannot edit. Pad to [0x00, 0x80, 0x00] and both decoders agree. For records under 1 kB you will never reach these values, but an implementation that gets it wrong will fail the first time somebody inscribes something large.

3.3 The commit / reveal chain

Script data cannot simply be dropped into an output. It is committed inside a P2SH redeem script and revealed when that output is spent:

redeemScript (the "lock")  =  push(pubkey) OP_CHECKSIGVERIFY
                              OP_DROP × (number of envelope chunks in this hop)
                              OP_TRUE

scriptPubKey (the commit)  =  OP_HASH160 push(hash160(lock)) OP_EQUAL

scriptSig (the reveal)     =  <envelope chunks for this hop> push(signature) push(lock)
MAX_CHUNK_LEN240 — data bytes per part
MAX_PAYLOAD_LEN1500 — serialized envelope-chunk bytes carried per transaction
MAX_SCRIPT_ELEMENT_SIZE520 — the redeem script is pushed as one stack item, so it must fit
INSCRIPTION_KOINU100000 (0.001 DOGE) — the value carried on the inscription output at every hop
signinglegacy SIGHASH_ALL over an arbitrary subscript — the lock script, not a P2PKH template

For a record at BEACON's ≤ 1000-byte target the whole envelope fits in one data-bearing transaction, so the chain is exactly two transactions:

tx[0]  "commit"        funds a P2SH output whose redeem script hides the envelope
tx[1]  "reveal-final"  spends it, exposing the envelope in scriptSig,
                       and pays 0.001 DOGE to vout 0 = THE INSCRIPTION
The inscription id is the FIRST data-bearing reveal, not the last. ord-dogecoin's inscription updater derives the id from the txid of the transaction whose input[0] scriptSig first carried the ord marker — that is tx[1]. For a two-transaction chain that is also the final reveal, so the two coincide and nothing surfaces. For a payload that spans many data transactions they are different, and recording the final txid gives you an id the indexer never assigns: an inscription you cannot find, on a chain you cannot edit. Keep a BEACON record in one data-bearing reveal — under ~1400 bytes — and the beacon txid, the ord inscription id and the reveal txid are all the same value. That is why §1 of the spec caps records at 1000 bytes.

3.4 Reading it back

Start at the reveal txid. For each hop, take vin[0]'s scriptSig, parse it into chunks, and discard the last two — those are the signature and the redeem script. If the first remaining chunk is "ord" you have reached the start; otherwise follow vin[0].txid back one hop. Then reverse the per-transaction order (you walked backwards), concatenate, and decode §3.1. Cap the walk — 64 hops is about 1 MB of payload — so a malformed chain cannot spin forever.

The discovery rule for finding BEACON records among all inscriptions is one line: content type begins text/plain and the first line is exactly BEACON/1. That is all. There is no registry of BEACON-issuing addresses, no allowlist, and nothing to join.

4 · Writing one yourself

Three routes, in increasing order of effort. All three produce byte-identical records; the chain cannot tell which you used, and neither can we.

Route A — any existing doginals inscriber

Because BEACON is an ordinary text/plain doginal, any tool that can inscribe a text file already inscribes BEACON records. Write the record to a file and inscribe it with the content type text/plain;charset=utf-8:

$ cat > biscuit.txt <<'EOF'
BEACON/1
T:PET
ID:ISO11784:985141001234567
N:Biscuit
1:Beagle mix, 24 lb, tan and white
2:White blaze, notched left ear, red collar with a bell
3:H. Elsner
4:+1-650-649-4265
5:San Jose, CA
6:Diabetic, needs insulin twice daily. Reward for safe return.
S:ACTIVE
EOF

# inscribe with your doginals tool of choice, e.g.
$ node . mint <your-doge-address> text/plain;charset=utf-8 biscuit.txt

The reveal txid it prints is your beacon's permanent identity. Nothing else is required for the record to exist.

Route B — the open browser core we use ourselves

The signing core this site runs is a single dependency-free JavaScript file served from the wallet host, and you may use it directly. Keys never leave the page; there is no server call in the build step.

<script src="https://doge.doginaldog.shop/dogewallet-core.js"></script>
<script>
const text = "BEACON/1\nT:PET\nID:ISO11784:985141001234567\nN:Biscuit\n…";
const hex  = [...new TextEncoder().encode(text)]
               .map(b => b.toString(16).padStart(2, "0")).join("");

const built = DogeWallet.buildInscription({
  wif:              "<your WIF — stays in this page>",
  contentType:      "text/plain;charset=utf-8",
  dataHex:          hex,
  recipientAddress: "<where the inscription should land>",
  changeAddress:    "<your change address>",
  utxos:            [ /* {txid, vout, value} — CARDINAL utxos only, see §5 */ ],
  feeRateKoinuPerKb: "200000000"        // 2 DOGE/kB
});

// built.txs = [{role, hex, txid, fee, size}, …]  — broadcast IN ORDER.
</script>

Broadcast each hex in order to any Dogecoin node or public broadcast endpoint. You do not have to use ours. If you want to, POST /capi/chain/broadcast with {"hex": "…"} is open and takes no key — it is a relay, and it is the only thing this box does for a visitor's transaction. It never sees a private key, because the transaction arrives already signed.

Route C — from scratch

§3 is complete. Implement the chunk encoder, the P2SH lock, legacy SIGHASH_ALL signing over the lock as subscript, and the funding walk. The two places implementations actually break are the sign-padded number encoding (§3.2) and the fee floor (§5) — not the cryptography.

Test on paper first. Build the transaction, then run your own reader from §1 against the unbroadcast hex before you spend anything. If your reader cannot recover your own payload, the chain will not either, and Dogecoin has no undo.

5 · Fees, dust and no-RBF

These are Dogecoin facts, not BEACON policy, and they are where real money is lost.

RuleWhy it bites
Dogecoin has no RBF.A broadcast transaction cannot be fee-bumped. There is no second chance to raise the fee. Overpay slightly, always.
The fee floor is charged per started kilobyte.A 1.87 kB reveal owes the 2 kB minimum. Paying 1.87 kB worth leaves it unmineable, permanently, with no way to fix it.
Every hop carries a sub-dust output.The 0.001 DOGE inscription output is below the normal dust relay threshold, so rate × size alone will not get the transaction mined. An absolute per-transaction floor must be applied on top.
Never fund from an inscription-bearing UTXO.Spending a UTXO that carries someone's doginal as ordinary change destroys it. Classify your UTXOs and fund only from cardinal ones. Fail closed: if you cannot classify a UTXO, do not spend it.
Persist before you broadcast.Write the signed hex somewhere durable first. If you must re-broadcast, re-broadcast the identical bytes; a second, differently-signed attempt is a double-spend race you cannot call off.
Unsticking is CPFP, not replacement.Spend the stuck transaction's output at a high fee. The only "cancel" is waiting for the mempool to drop it.

This site inscribes at 200000000 koinu/kB (2 DOGE/kB), which at BEACON record sizes costs a few US cents in total for the whole two-transaction chain. You are free to pick your own rate; the protocol has no opinion.

6 · Authority and the fold

There is no account, no password and no registrar. The access-control model is one sentence: the holder of the genesis inscription output is the authority.

An update — a record carrying P:<genesis txid> — is honoured if and only if it was inscribed by one of:

  • an address that has held the genesis inscription (the custody chain, not merely the current holder — an update made by a past holder was authorised at the time it was made), or
  • the address named in the genesis record's A key, if present.

"Inscribed by" means: an address that appears among the inputs funding the update transaction.

An unauthorised update is recorded, not discarded. An indexer MUST show it, flagged as unauthorised, rather than silently dropping it. A disputed record must be visible. An indexer that hides contradiction is making an editorial decision, and editorial decisions are exactly what this protocol exists to remove.

The fold algorithm

current = genesis.keys                        # start from the genesis record
authority = set(custody_chain) | ({genesis.A} if genesis.A else set())

for u in updates sorted by (block_height, block_time):   # unconfirmed last
    if not (set(u.inscriber_addresses) & authority):  reject(u, "not authorised")
    elif u.ID and id_key(u.ID) != id_key(genesis.ID):  reject(u, "changes ID")
    elif u.T  and u.T != genesis.T:                    reject(u, "changes profile")
    else:
        for k, v in u.keys:  if k != "P":  current[k] = v   # present keys only

Absent keys keep their previous value — an update is a patch, not a replacement. ID and T may be echoed but never changed: the identifier is what the beacon is.

Two consequences a writer must understand before spending:

  • Transferring the inscription transfers the beacon. Sending the doginal hands the next holder the right to rewrite the record. For an asset that is the point: custody of the token is custody of the registration.
  • Losing the key freezes the record. It stays readable forever and can never be updated again. The remedy is a new genesis, cross-referenced from the old one with X.

7 · How indexing actually works

This is the part people assume is magic, so here is the plain mechanism, with its current limits stated rather than glossed.

Any valid BEACON record enters this index regardless of who wrote it, what tool they used, or whether they have ever heard of this site. The index has no concept of "our" records versus other people's. There is one code path, and these are the three ways a txid reaches it:

RouteTriggerWho can use it
SubmissionPOST /api/index {"txid": "…"}Anyone. No key, no account. Rate-limited to 20/min per IP purely to keep the chain indexer from being hammered.
Resolution on first readGET /api/record/<txid> or GET /r/<txid> for a txid never seen beforeAnyone. Being "in our index" is not a precondition for resolving. The record is fetched from the chain, decoded, folded and stored on the spot.
Custody sweepAutomatic, on refresh of an existing beaconNobody has to do anything. Every address that has held the beacon is swept for further BEACON inscriptions naming it in P, so an owner who updates from the wallet holding the doginal is picked up without submitting anything.
Stated honestly: there is no full-chain crawler today. The index is submission-and-read driven plus the bounded custody sweep above (4 addresses × 60 transactions per refresh, 5-minute cache TTL). A record inscribed by a stranger who never tells anyone the txid, and whose txid nobody ever looks up, will sit on the chain correctly and unlisted. That is a gap in this cache, not in the protocol — the record is real, complete and readable the whole time. The discovery rule in §3.4 is published precisely so that anyone, including you, can build the crawler that closes it. If you do, nothing needs to change here and you do not need our permission.

And the reason the gap is survivable: the index is a cache with no authority. Every field in it is derived from Dogecoin and can be rebuilt from Dogecoin. Delete /srv/data/beacon and nothing is lost but speed. Any index that starts deciding things instead of caching them has broken the guarantee.

8 · Resolver API reference

Base URL: https://pet.doginaldog.shop (pet vocabulary) or https://beacon.hankelsner.tech (asset vocabulary). One service, two front doors, identical data — the Host header picks the labels and theme, never the content. All responses are JSON unless noted. There is no authentication anywhere on this API.

GET/api/health
$ curl -s https://pet.doginaldog.shop/api/health
{"ok": true, "service": "beacon", "spec": "BEACON/1"}
GET/api/stats

Index-wide counts. Cached 30 s.

{"beacons": 0, "by_status": {}, "by_profile": {}, "missing": 0}
GET/api/profiles

The complete machine-readable vocabulary: profiles and their six line labels, status codes and per-profile display labels, every identifier scheme with its hint, and the field length limits. Cached 1 h. Fetch this instead of hard-coding.

GET/api/spec.md

The specification itself, as Markdown. text/markdown; charset=utf-8.

GET/api/record/<txid>

The folded record. ?refresh=1 forces a re-check against the chain (rate-limited 20/min/IP; otherwise a 5-minute TTL applies). A txid that has never been indexed is resolved from the chain on the spot.

{
  "txid": "<64 hex>",
  "inscription_id": "<64 hex>i0",
  "view":    { "name": …, "id": …, "status": …, "status_label": …,
               "profile": "PET", "lines": [ {"label": …, "value": …}, … ] },
  "keys":    { "T": "PET", "ID": "…", "N": "…", "1": "…", … },  ← after folding
  "genesis": { "keys": …, "unknown": …, "meta": { "text": …, "bytes": …,
               "block_height": …, "block_time": …, "confirmations": …,
               "inscribers": ["D…"] } },
  "holder":  { "address": "D…", "traced": true, "custody_chain": ["D…"],
               "hops": 0, "note": null },
  "authority": ["D…"],
  "history": [ { "txid": …, "authorised": true, "changes": {"S": "MISSING"},
                 "block_height": …, "text": … } ],
  "applied": 1,
  "rejected": [ { "txid": …, "reason": "not authorised" } ],
  "first_seen": …, "checked": …
}

holder.traced: false means the transfer trace stopped early (the hop cap is 32) — the address shown may not be current, and a client MUST say so rather than present it as fact.

GET/api/lookup?q=&profile=&status=&limit=

Search the index. q matches, in priority order: exact normalised identifier → txid prefix → substring of the name → substring of the identifier. limit caps at 200.

$ curl -s 'https://pet.doginaldog.shop/api/lookup?q=985141001234567'
{"results": [ {"txid": …, "id": …, "name": …, "profile": "PET",
               "status": "ACTIVE", "holder": "D…"} ], "count": 1}
GET/api/recent?limit=&profile=

Newest records first, by block time. limit caps at 100. Cached 30 s.

GET/api/qr.svg?text=&px=

A QR code as SVG. text 1–900 chars, px 64–1024. Cached 24 h.

GET/api/cert/<txid>.svg

Renders the certificate for a record. Query: theme, title, lines (e.g. 1234), qr=0|1, target (§10), value, host.

GET/r/<txid>

The human-facing record page — rendered entirely on the server with no client JavaScript. That is deliberate: a scanned QR code has to resolve on a stranger's locked-down phone, through a link-preview bot, on a bad connection, at 2 a.m. Anything that depends on a script executing is a lost pet. Carries og: tags and robots: index,follow. Cached 60 s.

POST/api/index

Ask the resolver to index a txid. This is the open door. No key. Costs nothing. Works for a record you inscribed with any tool, from any wallet, with no prior relationship to this site.

$ curl -s -X POST https://pet.doginaldog.shop/api/index \
       -H 'Content-Type: application/json' \
       -d '{"txid":"<your reveal txid>"}'
{"ok": true, "record": { … the folded record … }}

Submit an update txid and it is attached to its genesis automatically — indexing that genesis first if this resolver has never seen it. Errors: 400 malformed txid or not a valid BEACON genesis, 429 over 20/min, 502 the chain indexer is unreachable (transient — retry; it is never proof the record does not exist).

POST/api/cert/preview

Render a certificate SVG from a record body without inscribing anything. 60/min/IP.

POST/capi/chain/broadcast

Relay a signed transaction: {"hex": "…"}. Belongs to the shared crypto engine, not to BEACON. Open, keyless, and optional — any Dogecoin node will do the same job.

9 · Build a competing indexer

This is an invited, expected outcome, not a threat. If you index BEACON better than we do, the protocol wins and the pets get found. Everything you need:

  1. Discover. Scan inscriptions whose content type starts text/plain and whose first line is exactly BEACON/1 (§3.4). Any ord-dogecoin index, doggy.market-style API, or your own node will do.
  2. Classify. No P key → genesis, keyed by its own reveal txid. Has P → update, belonging to the genesis it names.
  3. Validate genesis. Require T, ID, N. Warn on scheme mismatches; never reject on them.
  4. Trace custody. Follow the 0.001 DOGE output from the genesis txid. A doginal transfer keeps the inscription on output 0 of the spending transaction — the convention every Dogecoin wallet follows. Record every address it passes through; that list is the authority set (§6).
  5. Fold. Apply §6 in block order. Publish the rejects.
  6. Index for lookup on id_key(ID) (§2), not the raw string.
  7. Never decide anything. If your index and the chain disagree, the chain is right and your index is broken.

One practical note that cost us real time: Blockbook has no outpoint→spender index. The cheap way to find the spender of an inscription output is to query the address holding it with details=txs&from=<block height> — one call returning full transactions, with everything before the funding block discarded. Because results come newest-first, reaching the funding transaction itself means everything remaining is older, so you stop there. The naïve version took 38 seconds on a busy wallet.

10 · Certificates

A beacon MAY additionally be minted as a visual certificate: a second inscription with content type image/svg+xml. It is decorative and evidentiary. It is not required to read or resolve a beacon, and an indexer must never depend on one.

A certificate MUST embed, inside the SVG's <desc> element, a self-describing block so an indexer can link it back to the record without trusting a filename:

<desc>BEACON/1 CERT
P:<genesis txid>
ID:<scheme>:<value>
N:<name></desc>

The genesis record MAY then be updated with C:<certificate txid> to point forward at it. A certificate MAY carry a QR code encoding exactly one of: txid, resolver (https://<host>/r/<txid>), explorer, contact (a tel:/mailto: from line 4), url (the record's U), or custom.

A resolver URL points at a resolver. If it is gone, the txid inside the certificate still resolves against the chain — which is why encoding the bare txid is the most durable choice and encoding only a resolver URL is the least.

11 · Conformance vectors

If your parser agrees with all of these, it agrees with ours. Every one of them encodes a rule that has a real failure behind it.

INPUT                                   EXPECTED
─────────────────────────────────────── ────────────────────────────────────────
"BEACON/1\nN:A\nN:B"                    N == "A"          (first duplicate wins)
"BEACON/1\r\nT:PET\r\n"                 T == "PET"        (CR stripped)
"BEACON/1\n  N : x "                    N == "x"          (the KEY is stripped too,
                                                           so "N " is key "N")
"BEACON/1\nN: Biscuit"                  N == "Biscuit"    (leading space gone)
"BEACON/1\nN:  Biscuit"                 N == "Biscuit"    (the value is FULLY
                                                           stripped: a value can
                                                           never begin or end
                                                           with whitespace)
"BEACON/1\n#N:x\nN:y"                   N == "y"          (comment ignored)
"BEACON/1\n\n\nN:y"                     N == "y"          (blank lines ignored)
"BEACON/1\nID:ISO11784:985141001234567" ID value ==
                                          "ISO11784:985141001234567"
"BEACON/1\nZZ:future"                   ZZ preserved in `unknown`, NOT an error
"BEACON/1\nnope"                        line ignored, record still valid
"beacon/1\nN:x"                         NOT a BEACON record (magic is exact)
" BEACON/1\nN:x"                        IS a BEACON record (leading WSP stripped)
"\n# hi\nBEACON/1\nN:x"                 IS a BEACON record (magic = first
                                          non-blank, non-comment line)
"BEACON/1\nT:PET\nID:CUSTOM:x\nN:n"     valid genesis
"BEACON/1\nP:<64 hex>\nS:MISSING"       valid update, changes S only

id_key("iso11784:985 141-001.234567") == id_key("ISO11784:985141001234567")
id_key("ISO11785:985141001234567")    != id_key("ISO11784:985141001234567")
                                         ^ the alias is NOT folded -- see the
                                           discrepancy note in section 2

number chunk 0      -> 0x00                 (OP_0)
number chunk 1      -> 0x51                 (OP_1)
number chunk 16     -> 0x60                 (OP_16)
number chunk 17     -> 0x01 0x11
number chunk 128    -> 0x02 0x80 0x00       (sign-padded)
number chunk 32768  -> 0x03 0x00 0x80 0x00  (sign-padded — see §3.2)

push of 3 bytes     -> 0x03 <data>
push of 240 bytes   -> 0x4c 0xf0 <data>     (PUSHDATA1)

The txids in the worked examples throughout this site are illustrative placeholders, not real records. At the time of writing the index holds the count shown by /api/stats — check it rather than trusting any number written on a page.

12 · Permanence rules

Permanent and public are the same sentence here. An update can supersede information; nothing can erase it.

  • No home street address. Line 5 is an area — a town, a postcode, a site name. A finder needs to know roughly where; nobody needs your door.
  • No government identity numbers, no payment details, no passwords.
  • Use a contact channel you are willing to keep or abandon — a phone number, an email address, or a URL you control. It is permanent once written.
  • Assume everything ever written stays readable. Superseding a phone number does not delete the old one; it only stops it being the current answer.

13 · For AI agents

There is a memory file written for you rather than for a person: a single self-contained document with the format, the rules, the endpoints, the failure modes and worked call sequences, in a form you can load into context and act from without reading this page.

/agent.md — the agent memory file served from the API too /llms.txt

If you are an agent acting for someone: you can compose and validate a record, resolve any beacon, and submit a txid for indexing entirely through the endpoints in §8, none of which need a key. You cannot sign for them — signing needs their private key, and that is deliberate. Nothing on this box will ever ask you for one, and any surface that does is not us.

14 · Licence and non-ownership

BEACON/1 is a specification, not a product. We do not own it, we cannot revoke it, and there is nothing to license. Implement it, fork it, compete with it, ship a better registry, charge for your version, or write a profile we never thought of — no attribution required and no permission asked.

The parts of this that are ours are the two ordinary things: the code we wrote, and the instance we operate. Both are replaceable. The chain is the registry; everything else, including this site, is a convenience that should be able to disappear without taking anything with it. If this page is ever unreachable, §1 through §6 are enough to rebuild all of it from the chain alone.