Exploded engineering blueprint of a delivery robot

schematic sheet 01 — hoodly technical documentation

Authentication

Humans sign in with a wallet signature; robots authenticate with an API key. Usage is metered by prepaid credits when a proof is anchored — not by holding a token balance.

Signing in with a wallet

Your wallet address is your account — there is no email or password. Sign-in is a two-step challenge/response, and signing the message is free: it sends no transaction and grants Hoodly no access to funds. When more than one browser wallet is installed, the login page lists them via EIP-6963 so you can pick which extension to use.

1. request the challenge
POST /api/auth/challenge
{ "address": "0x7f3a...c21e", "chainId": 4663 }

→ 200 OK
{ "message": "hoodly.fun wants you to sign in with your Ethereum account:\n0x7f3A...c21E\n..." }

Sign that exact string with the wallet (personal_sign), then post the signature back:

2. verify the signature
POST /api/auth/verify
{ "address": "0x7f3a...c21e", "signature": "0x..." }

→ 200 OK  (sets the hoodly_session cookie)
{ "signed_in": true, "wallet": "0x7f3a...c21e", "gate": { "holds": true } }

The message follows the EIP-4361 layout and carries a one-time nonce that is consumed on verification, so a signature can never be replayed. The session token lives in an HttpOnly cookie for 30 days; only its SHA-256 hash is stored server-side. POST /api/auth/logout revokes it.

Which network the wallet is on

chainId is optional and should be the network your wallet is actually connected to. It only affects the Chain ID line of the message: wallets such as Phantom validate that line against their active network and refuse to display a request that disagrees with it. Omit the field and the message is bound to Robinhood Chain.

No wallet ships with Robinhood Chain preconfigured, so the dashboard offers to add and switch to it before asking for a signature. The network details come from a public endpoint, in the exact shape wallet_addEthereumChain expects:

network details
GET /api/chain

→ 200 OK
{
  "chain_id": 4663,
  "name": "Robinhood Chain",
  "testnet": false,
  "explorer_url": "https://robinhoodchain.blockscout.com",
  "params": {
    "chainId": "0x1237",
    "chainName": "Robinhood Chain",
    "nativeCurrency": { "name": "Ether", "symbol": "ETH", "decimals": 18 },
    "rpcUrls": ["https://rpc.mainnet.chain.robinhood.com"],
    "blockExplorerUrls": ["https://robinhoodchain.blockscout.com"]
  }
}

Staying on another network does not lock you out. The signature only proves you control the address and is never submitted to a chain, and your $HOODLY balance is always read from Robinhood Chain server-side.

API keys

Each robot has its own API key, created when you register the robot in the dashboard. Keys look like this:

hdly_4f3c2a1b9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b

Hoodly stores only a SHA-256 hash of the key. The plaintext is shown exactly once at creation — if you lose it, register a new robot and rotate the key in your fleet.

Using a key

Send the key as a bearer token on every request to /api/v1/*:

request header
Authorization: Bearer hdly_your_api_key

Error responses

401 — missing or invalid key
{
  "error": "invalid_api_key",
  "message": "The provided API key is invalid or has been revoked."
}
402 — out of credits
{
  "error": "no_anchor_quota",
  "message": "The owning account has no anchor quota left. Burn $HOODLY to the address on the billing page. See https://hoodly.fun/docs/credits"
}

A shortfall of gas credit returns no_gas_credit with the same status. The task stays claimed either way — top up and complete again. See credits.

Best practices

One key per robot. Keys identify the machine — do not share one key across a fleet.
Keep keys out of firmware images. Inject them via environment or a secrets manager, so a leaked image does not leak the key.
Rotate on suspicion. Register a replacement robot and delete the old one; its key stops working immediately.