Skip to main content
Every /v1 call (except minting a key) needs an API key:
X-Api-Key: hk_test_... works too. Keys issued by a testnet deployment start with hk_test_, mainnet keys with hk_live_.

Scopes

Only the endpoints above check a scope. Every other /v1 endpoint — reads, wallets, threads, swaps, settlement builders, broadcast, webhooks — works with any valid key; threads and swaps are readable only by their two parties. A call that needs a scope the key lacks returns 403. GET /v1/me shows the key’s scopes.

Key rules

  • Expiry: a key works for 90 days (expiresAt in 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.
  1. GET /v1/keys/nonce → { "nonce": "…" }. The nonce is single-use and lives 5 minutes.
  2. Sign a message with the wallet (rules below).
  3. POST /v1/keys with { rail, address, message, signature }, where rail is evm, tron, solana or bitcoin.
The key belongs to the account that wallet signs in to, created on first use. A wallet proven onto another account does not reach that account.

The message

The server checks: 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 proven wallet on that asset’s chain. 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.
  1. GET /v1/wallets/nonce → a single-use link nonce.
  2. Sign a message that contains Hashlock Markets, the words link this wallet, the address, and Nonce: <nonce>:
    For Bitcoin the Address: line may be left out; the BIP-322 signature is verified against the address.
  3. 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. 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 widens what the account can trade. It does not count as a place to receive money. 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. For Bitcoin, the pubkey of such an address.
On EVM and TRON a refund returns to whoever funded, so the refund address only names the funder and is not restricted. This way a leaked key cannot point your money at the thief’s wallet. If you need to be paid at a wallet that is not your login wallet, prove it from the web app while signed in.