TimeBinder API
Submit file hashes to be timestamped on the Bitcoin blockchain and retrieve your TimeBinds from your own systems. Files never leave your servers — you hash them with SHA-256 and send only the hashes.
Base URL: https://api.timebinder.io/v1
All requests and responses are JSON. Successful responses wrap their payload in data.
TimeBinds created through the API are charged to your token balance at the same rates as the app and appear in your TimeBinder dashboard.
API keys are secrets. Call the API from your servers only — never embed a key in a browser or mobile app.
Authentication
Create keys at app.timebinder.io/api. Each key has an optional expiry date and its own permissions:
| Permission | Allows |
|---|---|
viewTimebinds | GET /v1/timebinds and GET /v1/timebinds/{id} |
submitPooled | POST /v1/timebinds with "priority": "pooled" |
submitAsap | POST /v1/timebinds with "priority": "asap" |
Send the key as a bearer token (an X-API-Key header is also accepted):
curl https://api.timebinder.io/v1/account \
-H "Authorization: Bearer tbk_..."
Errors
Errors use standard HTTP status codes and this body:
{ "error": { "code": "permission_denied", "message": "This API key does not have the \"submitAsap\" permission." } }
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The request body or query is invalid. |
| 401 | missing_api_key, invalid_api_key, api_key_disabled, api_key_expired | The key is missing, unknown, disabled or past its expiry date. |
| 402 | insufficient_tokens | Your token balance is too low for this submission. |
| 403 | permission_denied, api_access_disabled | The key lacks the permission, or API access is not enabled on the account. |
| 404 | not_found | The TimeBind or endpoint does not exist. |
| 500 | server_error | Something went wrong on our side. Retry later. |
Get account
Returns your token balance, the token cost of each priority and details of the key in use. No permission required.
{
"data": {
"tokens": 42,
"charges": { "asap": 4, "pooled": 1 },
"apiKey": {
"id": "Xk2...", "name": "Production", "keyPrefix": "tbk_a1B2c3D4",
"expires": null,
"permissions": { "viewTimebinds": true, "submitPooled": true, "submitAsap": false }
}
}
}
Submit a TimeBind
Creates a TimeBind from up to 10 file hashes and charges your token balance. The Merkle root of the file hashes, reference and details is what gets anchored on-chain.
| Field | Type | Description |
|---|---|---|
files | array, required | 1–10 entries of { "filename", "hash", "size"?, "fileType"?, "lastModified"? }. hash is the 64-character hex SHA-256 of the file contents. |
priority | string | "pooled" (default) batches with other TimeBinds; "asap" gets its own transaction. |
reference | string | Your reference, up to 5000 characters. Hashed into the TimeBind. |
details | string | Free-text details, up to 5000 characters. Hashed into the TimeBind. |
permission | object | { "type": "private" } (default) or { "type": "public" } (anyone with the link can view the verification page). Any other type is rejected with 400 invalid_request. |
identity | object | Optional { "name", "email" } recorded as additional hashed leaves. |
curl -X POST https://api.timebinder.io/v1/timebinds \
-H "Authorization: Bearer tbk_..." \
-H "Content-Type: application/json" \
-d '{
"priority": "pooled",
"reference": "Contract 2026-114",
"files": [
{ "filename": "contract.pdf", "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "size": 48213 }
]
}'
Responds 201 Created with the new TimeBind, the tokens charged and your remaining balance:
{ "data": { "id": "aB3...", "status": "pending", ... }, "tokensCharged": 1, "tokens": 41 }
List TimeBinds
Lists all TimeBinds on your account (including those created in the app), newest first.
| Query | Description |
|---|---|
status | Filter: pending_payment, pending, processing, submitted, confirmed or error. |
limit | 1–100, default 25. |
startAfter | The nextCursor from the previous page. |
{ "data": [ { "id": "aB3...", ... } ], "hasMore": true, "nextCursor": "aB3..." }
Get a TimeBind
Returns one TimeBind. TimeBinds belonging to other accounts return 404.
TimeBind object
{
"id": "aB3...",
"status": "confirmed", // pending_payment | pending | processing | submitted | confirmed | error
"priority": "pooled", // pooled | asap
"hash": "e3b0c4...", // Merkle root of all leaves
"hashMethod": "SHA-256",
"reference": "Contract 2026-114",
"details": "",
"permission": { "type": "private", "emailaccess": [] },
"submitted": "2026-10-29T01:02:03.000Z",
"updated": "2026-10-29T03:04:05.000Z",
"transacted": "2026-10-29T02:00:00.000Z", // broadcast on-chain
"confirmed": "2026-10-29T03:04:05.000Z",
"payMethod": "tokens",
"tokens": 1,
"source": "api", // api | app
"leaves": [
{ "type": "file", "name": "contract.pdf", "hash": "9f86d0...", "size": 48213,
"merkleProof": [ { "position": "right", "hash": "..." } ] }
],
"blockchain": { "txId": "4a5e1e...", "network": "mainnet", "status": "completed", "confirmations": 6 },
"pool": { "id": "pool_...", "hash": "...", "merkleProof": [ ... ] } // pooled TimeBinds only
}
Verifying a file
Start with the file's SHA-256 hash and apply each merkleProof step: for "right" compute
sha256(current + step.hash), for "left" compute sha256(step.hash + current)
(hex strings concatenated). The result is the TimeBind hash. For pooled TimeBinds, continue from that
hash with pool.merkleProof to reach pool.hash, which is the value in the transaction's OP_RETURN output.
Callbacks
Each key can have two callback URLs (HTTPS, publicly reachable). TimeBinder sends a POST for
TimeBinds created with that key:
| Event | When |
|---|---|
timebind.submitted | The TimeBind has been accepted for processing (status pending). May arrive before the POST response. |
timebind.confirmed | The TimeBind is complete: its transaction has 6 confirmations on the Bitcoin blockchain (status confirmed, blockchain.txId set). |
POST https://your-server.example.com/timebinder
Content-Type: application/json
X-TimeBinder-Event: timebind.confirmed
X-TimeBinder-Signature: t=1793232000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
{ "id": "evt_...", "type": "timebind.confirmed", "created": "2026-10-29T03:04:05.000Z", "test": false, "data": { ...TimeBind } }
Respond with any 2xx status. Non-2xx responses, timeouts (10 s) and redirects are treated as failures
and retried twice (after 2 s and 8 s). Use the event id to ignore duplicates.
Verifying the signature
Each key has a callback signing secret (whsec_...), shown on the API page. Compute an HMAC-SHA256 of
{t}.{raw request body} with the secret and compare it to v1. Reject requests whose
timestamp is more than a few minutes old.
// Node.js / Express
const crypto = require('crypto');
app.post('/timebinder', express.raw({ type: 'application/json' }), (req, res) => {
const header = req.get('X-TimeBinder-Signature') || '';
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const expected = crypto.createHmac('sha256', process.env.TIMEBINDER_CALLBACK_SECRET)
.update(`${parts.t}.${req.body}`)
.digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
if (!fresh || String(parts.v1).length !== expected.length || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))) {
return res.sendStatus(400);
}
const event = JSON.parse(req.body);
// handle event.type / event.data
res.sendStatus(204);
});