REST API.
For teams that want their own interface rather than the embed widget. The API prepares transactions; the user's wallet still signs and submits them, so nothing here can move funds.
Base URL and format#
All endpoints live under https://hoodlock.tech/api/dev, take and return JSON, and send
permissive CORS headers so they work from a browser as well as a server.
These routes carry X-Robots-Tag: noindex, which is why this page, not the JSON —
is the documented surface.
Authentication#
One credential: a public API key of the form pk_ followed by 48 hex characters. You get
it by registering a developer handle in the developer dashboard.
| Method | How the key is passed |
|---|---|
GET | query string — ?key=pk_… |
POST | a key field in the JSON body |
There is no header form. The CORS policy allows Content-Type only, so an
Authorization header will be blocked on cross-origin requests. Use the query string or the
body field.
The key is public on purpose, in the same sense as a Stripe publishable key. It credits locks to you and does nothing else. It cannot move funds, read balances or reach admin. Claiming earnings requires a signature from your wallet, which the key cannot produce. Embedding it in frontend source is the intended use.
GET /api/dev/config#
Everything your interface needs to build a transaction. Call it once at boot rather than hardcoding addresses or fees — the fee is read from the contracts and can change.
curl "https://hoodlock.tech/api/dev/config?key=pk_YOUR_KEY"{
"chainId": 4663,
"rpc": "https://rpc.mainnet.chain.robinhood.com",
"explorer": "https://robinhoodchain.blockscout.com",
"locker": "0xd0f7d8c6e9f6d80c297bebe4f7fd1b9c8125c32f",
"burner": "0x6bf43ca706faa8ea46803299c191484e82280652",
"vesting": "0x910e19bcC4bce46999994Ed7297E0Fc4431ec72E",
"fees": { "lock": "5000000000000000", "burn": "5000000000000000", "vesting": "5000000000000000" },
"feesEth": { "lock": 0.005, "burn": 0.005, "vesting": 0.005 },
"commission": 0.5,
"code": "your-handle"
}feeWei and feeEth are also present at the top level and hold the
lock fee; they predate the per-product fields and are kept so older integrations keep working.
Prefer fees. A product that is not configured on the chain returns null.
Errors: 404 {"error":"unknown key"}, or 503 if the database is unavailable.
Transaction intents#
Each intent validates your inputs, encodes the call and returns an unsigned transaction. You submit it from the user's wallet.
{ "to": "0x…", "data": "0x…", "value": "5000000000000000", "chainId": 4663, "note": "…" }For locks and vesting the token must be approved for to first — the intent covers
the HoodLock call, not the ERC-20 approval. The note field in the response says so too.
POST /api/dev/lock-intent#
| Field | Type | Notes |
|---|---|---|
key | string | Your API key. |
token | address | The ERC-20 to lock. |
amount | string | Integer string, in the token's smallest unit. |
unlockTime | string | Unix seconds. Must be in the future. |
curl -X POST https://hoodlock.tech/api/dev/lock-intent \
-H "content-type: application/json" \
-d '{"key":"pk_YOUR_KEY",
"token":"0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73",
"amount":"1000000000000000000000",
"unlockTime":"1790000000"}'POST /api/dev/burn-intent#
Fields: key, token, amount. Returns 404 if
burning is not configured on the chain.
Burning is irreversible. Show the user what is about to happen in your own UI — the intent endpoint will not stop them.
POST /api/dev/vesting-intent#
| Field | Type | Notes |
|---|---|---|
key | string | Your API key. |
token | address | The ERC-20 to vest. |
amount | string | Integer string, smallest unit. |
beneficiary | address | Who receives the tokens. |
end | string | Unix seconds. Must be after start. |
start | string, optional | Defaults to now. |
cliff | string, optional | Defaults to start. Must satisfy start ≤ cliff ≤ end. |
The contract's rules are checked here, so a schedule that would revert generally never reaches a signature prompt.
One case slips through. This endpoint checks end - start ≥ 86400, while the
contract checks end > block.timestamp + MIN_DURATION. A back-dated start can
satisfy the API and still revert on-chain. Validate against block.timestamp in your own UI.
POST /api/dev/attribute#
Credits a wallet to your handle so locks it makes afterwards earn you commission. Call it when the user connects, before they transact.
curl -X POST https://hoodlock.tech/api/dev/attribute \
-H "content-type: application/json" \
-d '{"key":"pk_YOUR_KEY","wallet":"0xUSER"}'Returns {"ok":true}, or {"ok":false,"reason":"…"} with HTTP 200 — a
refusal is an expected outcome, not an error.
| Reason | Meaning |
|---|---|
bad-wallet | Not a valid address. |
already-attributed | Another partner got there first. First touch wins and is permanent. |
established-wallet | The wallet was already known to HoodLock before you introduced it. |
already-locked | The wallet has locked, burned or vested before. |
unknown-code | The handle behind the key no longer exists. |
Only genuinely new wallets count, and only actions taken after attribution. That is deliberate: it stops a partner from claiming users who were already here. It also means calling this endpoint on every page load is harmless — repeats are refused, not double-counted.
Rate limits#
| Endpoint | Requests per minute per IP |
|---|---|
/api/dev/attribute | 30 |
*-intent | 60 |
Over the limit returns 429 {"error":"rate limited"}.
A minimal integration#
const cfg = await (await fetch(`/api/dev/config?key=${KEY}`)).json();
// after the user connects
await fetch("https://hoodlock.tech/api/dev/attribute", {
method: "POST", headers: { "content-type": "application/json" },
body: JSON.stringify({ key: KEY, wallet: account }),
});
// 1. approve, 2. lock
const intent = await (await fetch("https://hoodlock.tech/api/dev/lock-intent", {
method: "POST", headers: { "content-type": "application/json" },
body: JSON.stringify({ key: KEY, token, amount, unlockTime }),
})).json();
await walletClient.sendTransaction({
to: intent.to, data: intent.data, value: BigInt(intent.value),
});Getting paid#
Partners earn 50% of the fee on every action generated by a wallet they introduced. Earnings accrue automatically and are claimed from the developer dashboard with a wallet signature, once the balance passes $10. The API key plays no part in that. See the embed widget page.