Sign with HeartBank®: reference
Let the people who use your app sign what they made, and let anyone check it. B-Origin℠.
Read from the live code: the signing request at sign.333.eco (services/sign/src/app.ts), the lookup at origin.333.eco (src/server.ts traceApi), and the package @333eco/origin-attest (index.mjs). The live guide is developers.333.eco/sign.
⛔ Sign is not Log in. Logging in says who someone is for a while; signing puts their passkey to one exact file, with a fresh passkey press every time. An app can ask; only the person can sign.
Signing, from a web page. Most apps use the package (npm install @333eco/origin-attest): beginSign(file, {filename, tool, source, rights?}) on the button, finishSign() on your return page. Under it: (1) your page posts the claim to POST https://sign.333.eco/sign/par; the browser states your page's origin, which must be a registered caller (ask on the dashboard: we add your site and one return page by hand). (2) Send the person to https://sign.333.eco/sign?request_uri=…, which shows them what they're signing and who asked. The file never leaves their device. (3) They come back to your return page with #index=<n>&state=…, or #declined&state=… if they went back without signing. Keep a plain Download button too: signing makes a permanent public record, so it is the person's choice each time. ⏳ Signing from a native app is not built (signing identifies a caller by the browser's origin).
Looking up a file from your server (B-Trace℠): POST /api/v1/trace. ⚠️ Metered: a lookup is paid from your business's balance once billing opens (the price we expect is $0.005 a lookup; free while this is being built, up to 2,000 a day). Checking a record is free forever, with no account: read the public log directly, or use @333eco/origin-verify (see the Verify description).
Prices: a record signed through your app, $0.01, and this may change before billing opens · checking any record, finding a file's record: free, forever · a person signing their own file: free, forever. No one ever pays to be proven human.
POST https://origin.333.eco/api/v1/trace
Look up what a file carries (B-Trace℠, metered)
Send the file as the body, up to 25 MB. We read it and forget it: nothing about the file is kept, only that one lookup happened. A failed lookup isn't counted.
Who may call it: A key from your business's API keys tab (it begins bo_live_), approved before it works. ⛔ Keep it on your server: a key inside an app isn't secret. A key can only look: it can never sign.
Body application/octet-stream
Body image/*
Answers
200 What the file carries. { fingerprint, records, c2pa, iptc, generators, watermarks, signal, notChecked }400 empty: send the file as the request body. { error, why }401 key: that key isn't one we know. { error, why }402 monthly_cap (your owner's own cap is reached) or balance_empty (the balance at billing.thonly.com is empty). { error, why }403 not_approved: the key is waiting to be approved. { error, why }413 too_large: the most is 25 MB. { error, why }429 daily_limit: 2,000 lookups a day while this is free. { error, why }503 busy (try again in a moment) or log (the public log can't be read right now, so no answer rather than a wrong one). { error, why }
What a success answer holds
| Field | Type | What it is |
|---|
fingerprint | string | The file's SHA-256. |
records | object[] | |
c2pa optional | object | The file's C2PA manifest when present; check.state is trusted (signer on the C2PA trust list; it still says only what the signer declared), valid (signer not on the list, so anyone could have made it) or invalid (failures says why). |
iptc optional | object | |
generators | string[] | |
watermarks | object[] | |
signal | number | B-Signal℠: the AI markers found in the file, recomputable from this same answer. ⛔ Never a measure of how human a work is, and never shown as a grade of someone's work (you agree to this when you make a key). |
notChecked | string[] | What this answer did not look for, on every answer (SynthID has no public check). |
On the MCP server: not a tool: metered, on the developer's own key; ⛔ the MCP server never passes a developer's key through (a developer tool may come with the sandbox)
POST https://sign.333.eco/sign/par
Ask a person to sign a file (from your web page)
Called from the BROWSER, on your registered page: the browser's Origin header is what identifies your app, so a server can't make this call for you. state comes back on your return page and never enters the log. The package's beginSign() makes this call.
Who may call it: anyone, no key
Body application/json
| Field | Type | What it is |
|---|
claim | { v, sha256, pixels, phash, mime, bytes, source, tool, declared, rights, imports, createdAtDeclared } | Built by claimFor() in the package; send it as built. It names the file by its fingerprints only. |
state optional | string | |
Answers
201 Send the person to https://sign.333.eco/sign?request_uri=<request_uri>. { request_uri, expires_in }402 paused: the balance that pays for this app, or its owner's monthly cap, has been reached. A person's own Sign on sign.333.eco is never touched. { error, why }403 origin: this page isn't a registered caller. { error, why }415 Send JSON. { error, why }429 rate_limited: see Retry-After. { error, why }
What a success answer holds
| Field | Type | What it is |
|---|
request_uri | string | |
expires_in | integer | |
On the MCP server: not a tool: a browser step: the browser's origin is the app's identity
GET https://sign.333.eco/sign
The page where the person signs
Shows what they're signing and who asked, then asks for their passkey. Returns the browser to your registered return page with #index=<n>&state=…, or #declined&state=…. Read it with the package's finishSign(), which gives back the same bytes either way. You may tell them, once and as a plain fact, that the record joins the public log within the hour. ⛔ Never show the time ticking away.
Who may call it: anyone, no key
Parameters
| Name | Type | What it is |
|---|
request_uri (query) | string | |
Answers
200 The signing page (HTML).
On the MCP server: not a tool: the person signs, with their own passkey