Hashlock Markets Keys API
The Keys API from Hashlock Markets — 2 operation(s) for keys.
The Keys API from Hashlock Markets — 2 operation(s) for keys.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/hashlock-markets:hashlock-markets-keys-api"
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Hashlock Markets Keys API
version: 1.0.0
description: 'Guides, concepts and a quickstart: https://docs.hashlock.markets
Non-custodial cross-chain atomic swaps (BTC ↔ EVM / TRON / Solana) via sealed RFQ + HTLC. Makers and takers share these endpoints. Authenticate with an API key: `Authorization: Bearer hk_...`. **Wallets first.** An order that GIVES an asset requires a proven wallet on that asset''s chain, and your key starts with only the address its account signed in with. `GET /v1/me` lists both what each chain''s `login` address is and what you have `proven`; `POST /v1/wallets/{evm|tron|solana|bitcoin}` proves another by signature — Bitcoin included, on a BIP-322 one. A proof adds trading reach and nothing else: it never becomes the account''s payout identity, which only a wallet session can change — so a key alone cannot redirect anyone''s money. The message you sign for it must say `link this wallet` and carry a nonce from `GET /v1/wallets/nonce`; that nonce is not a login nonce, so the signature you collect is not a session for that wallet''s account. **Rate limits.** Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds). Over the limit returns `429` with `Retry-After`. **Idempotency.** Send an `Idempotency-Key` header on any POST to make retries safe: the operation runs once and the same response is replayed (with `Idempotency-Replayed: true`). Reusing a key with a different body returns `422`. Keys are remembered for 24h — except a `504`, which is a read of ours running out of time: on the builders nothing happened, and on `/tx/broadcast` we cannot tell — so either way a retry with the same key really retries. The fund, claim and refund builders change nothing on the server, so they run on every call and nothing of theirs is remembered — retry them freely. **Custody-agnostic settlement.** The fund/claim/refund endpoints return UNSIGNED transactions; you sign with your own key/HSM and submit via `/v1/tx/broadcast`. The server never holds your keys. ---
## Maker feed (WebSocket)
Polling `GET /v1/rfqs` will not win you flow. `wss:///v1/ws` — the host you selected above — streams every request-for-quote you are eligible for, and takes your quotes back over the same socket. **Handshake.** Connect, then send your key as the first frame. Nothing else is accepted until you do:
```json
{ "apiKey": "hk_live_..." }
```
The key needs the `maker` scope (`GET /v1/me` shows what yours has). You get a snapshot, then a readiness marker:
```json
{ "type": "snapshot", "rfqs": [ ... ] }
{ "type": "ready" }
```
The snapshot is the **public** book only, and at most **100** entries, newest first. If you need more than that, page `GET /v1/rfqs?cursor=…` after connecting. An RFQ created while the snapshot was being built may appear both in it and in the stream that follows — frames carry the rfq id, so key on it and the duplicate is a no-op. **The stream.** Two kinds, and only two:
```json
{ "type": "rfq", "kind": "created", "rfq": { ... } }
{ "type": "rfq", "kind": "cancelled", "rfq": { ... } }
```
`cancelled` means the order left the book, not always that its owner cancelled it: when a listed token **closes** (it no longer passes the listing checks), every public order on it is sent as `cancelled` while its status is unchanged (`open` or `negotiating`); if the token **reopens**, the same orders come back as `created` with their original id and `createdAt`. Treat both as upserts keyed on the id. An RFQ also leaves the book when it **expires** or is **agreed** with another maker, and neither emits a frame. A book you maintain purely from the stream therefore drifts: it keeps orders nobody can take any more, and quoting into one is rejected. Reconcile with a fresh `snapshot` (reconnect) or check `GET /v1/rfqs/{id}` before committing to a price. **What reaches you.** Public RFQs, plus private ones addressed to an address on your account.'
servers:
- url: https://api.hashlock.markets
description: production
security:
- ApiKey: []
tags:
- name: Keys
paths:
/v1/keys/nonce:
get:
summary: 'Start minting an API key: a single-use nonce (5 minutes). No key needed'
security: []
responses:
'200':
description: ok
content:
application/json:
schema:
type: object
properties:
nonce:
type: string
operationId: getKeysNonce
tags:
- Keys
/v1/keys:
post:
summary: Mint an API key with a wallet signature — no browser, no key needed (agents…
description: 'Sign, with the wallet, a message that says `create an API key`, names the address and carries a nonce from `GET /v1/keys/nonce`. Optional lines `Key name: ` and `Scopes: read, taker` (default all three) decide the key — they are read from the signed text only. The key belongs to the account that wallet SIGNS IN to (created on first use); a wallet proved onto another account does not reach it. An account with only a wallet holds ONE live key: a new signed mint replaces the previous one (also the way to recover a lost or leaked key). An account signed up at /developers with email, Google or Telegram holds up to 10. All keys of an account share one rate budget. It expires after 90 days (`expiresAt`; mint a new one to renew), an account holds at most 10 live keys, the owner is told (in Telegram, when linked) of every new one in Telegram, and the plaintext key is returned once. Signing rules per rail are those of POST /v1/wallets/.'
security: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- rail
- address
- message
- signature
properties:
rail:
type: string
enum:
- evm
- tron
- solana
- bitcoin
address:
type: string
message:
type: string
signature:
type: string
example:
rail: evm
address: 0xYourAddress
message: 'Hashlock Markets — create an API key.
Address: 0xYourAddress
Key name: my-agent
Scopes: read, taker, maker
Nonce: <GET /v1/keys/nonce>
Issued At: 2026-09-30T12:00:00Z'
signature: 0x…
responses:
'200':
description: the key — shown once
content:
application/json:
schema:
type: object
properties:
id:
type: string
key:
type: string
prefix:
type: string
scopes:
type: array
items:
type: string
name:
type:
- string
- 'null'
expiresAt:
type: string
format: date-time
'400':
description: bad signature, spent nonce, or the account already holds 10 live keys
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
operationId: postKeys
tags:
- Keys
components:
schemas:
Error:
type: object
properties:
error:
type: string
required:
- error
securitySchemes:
ApiKey:
type: http
scheme: bearer
description: 'An API key (hk_live_… / hk_test_…) with read/taker/maker scopes. `read` is read-only: every write except webhooks needs `taker` or `maker` (403 otherwise), and either of those can read too. Creating RFQs and listing tokens need `taker`; quoting needs `maker`.'