/v1 call (except minting a key) needs an API key:
X-Api-Key: hk_live_... works too. Keys issued by a testnet deployment start with hk_test_, mainnet keys with hk_live_.
Scopes
A key holding only
read gets 403 (“this API key is read-only”) on any write except webhooks. Creating RFQs and listing tokens need taker specifically; quoting needs maker. Threads and swaps are readable only by their two parties. GET /v1/me shows the key’s scopes.
Key rules
- Expiry: a key works for 90 days (
expiresAtin the mint response). To renew, mint a new one. - Shown once: the plaintext key is returned once. The server stores only its hash and a display prefix.
- Owner is told: every new key is announced to the account owner in Telegram, when Telegram is linked.
- Wallet-only accounts hold 1 key. An account with only a wallet (an agent that minted by signature, or a wallet sign-in) holds one live key. A new signed mint replaces the previous key, which stops working. This is also how an agent recovers a lost or leaked key.
- Person-backed accounts hold up to 10. An account signed up at /developers with email, Google or Telegram holds up to 10 live keys. Minting an eleventh is refused until one is revoked.
- One rate budget per account: all keys of an account share it. More keys do not buy more rate. See Rate limits.
Mint a key in the browser
Sign in at hashlock.markets/developers and create a key. Revoke keys there too.Mint a key with a wallet signature
No browser, no existing key. This is the path for agents.GET /v1/keys/nonce→{ "nonce": "…" }. The nonce is single-use and lives 5 minutes.- Sign a message with the wallet (rules below).
POST /v1/keyswith{ rail, address, message, signature }, whererailisevm,tron,solanaorbitcoin.
The message
The name and scopes are read from the signed text only. What the wallet signed is exactly what is minted.
Signing per rail is the same as for proving wallets: EVM
personal_sign (EIP-191), TRON signMessageV2, Solana ed25519 signMessage (base58 signature), Bitcoin BIP-322.
replaced: true means a previous key of this account was revoked by this mint.
Minting is limited per client IP: 20 mint requests per hour.
Proving wallets
An order that gives an asset needs a wallet the account can fund that leg from. A key starts with only the address its account signed in with.GET /v1/me lists, per chain, the login address and the proven ones.
Over an API key, a wallet proved below lets the account give on EVM (set it as the refund address on the leg — it sends the fund). On TRON and Solana it does not: those legs are funded from the login wallet, or one authorised by it or linked in a signed-in session at hashlock.markets. On Bitcoin any wallet can pay the escrow, but its refund key must be one of those wallets’. An RFQ, quote or accept that gives on a chain the account cannot fund from is refused.
-
GET /v1/wallets/nonce→ a single-use link nonce. -
Sign a message that contains
Hashlock Markets, the wordslink this wallet, the address, andNonce: <nonce>:For Bitcoin theAddress:line may be left out; the BIP-322 signature is verified against the address. -
POST /v1/wallets/{evm|tron|solana|bitcoin}with{ address, message, signature }.
Responses:
200 proven (repeating a proof you hold is also 200); 400 bad signature, missing link this wallet, or a bad nonce; 409 the wallet belongs to another account.
The link nonce is not a login nonce, and a key-mint nonce is not either. Each purpose has its own nonce pool, so a signature collected for one cannot be replayed for another. A link signature is not a session for that wallet’s account.
Authorising a wallet on another chain
A proof alone lets the account give from that wallet on EVM. To make a wallet count everywhere — you are paid at it, refunded at it, and a TRON leg funds from it — add an authorisation signed by the account’s login wallet:-
GET /v1/wallets/nonce→ a link nonce, andGET /v1/wallets/nonce?purpose=authorize→ an authorisation nonce. -
Sign the link message (above) with the new wallet, and this message with the login wallet:
-
POST /v1/wallets/{family}with{ address, message, signature, authorization: { address, message, signature } }, whereauthorization.addressis the login wallet.
authorised: true. An authorisation signed by any wallet other than the account’s login wallet is refused (400). Sign both within 5 minutes; each nonce is single-use. Only authorise wallets whose keys you alone hold: an authorised wallet can also sign in to the account.
On TRON a leg is always funded from one wallet: the login TRON wallet, else the oldest one authorised by the login wallet or linked at hashlock.markets.
DELETE /v1/wallets/{address} withdraws a proof. Login addresses are not proofs and cannot be removed this way. Putting a proof back needs a new signature from that wallet.
Payout addresses
A proof made with an API key alone widens what the account can trade. It does not count as a place to receive money unless it carries the login wallet’s authorisation. Over the API,POST /v1/swaps/{id}/address accepts an address that receives money — the payout address, and on Bitcoin and Solana the refund address too — only if it is:
- a wallet the account signed in with, or
- a wallet proven from a signed-in wallet session on the web, or
- a wallet proven with the login wallet’s authorisation (above).