RFQ API
The RFQ API gives programmatic access to request-for-quote trading on Silhouette: takers request quotes and accept them, makers price and fill them, and both sides manage balances, deposits, and withdrawals. It has two surfaces:
- The REST API for authentication, instruments, tokens, RFQs, quotes, balances, and funding. Every mutation goes over REST.
- The WebSocket API for live RFQ, quote, balance, and price streams.
Both share one authentication model, documented below. To see the whole flow end to end, follow your first trade. For a plain-language explanation of how RFQs work, see the RFQ overview.
Base URL
The production API host:
https://rfq-api.silhouette.exchange
REST endpoints live under /v1. The shared WebSocket client stream is wss://rfq-api.silhouette.exchange/v1/rfq/ws; makers publish price ladders to a separate maker-only socket at /v1/rfq/prices/ws.
Specifications
Both API surfaces are described by machine-readable specifications, which are the source for the interactive API Reference:
| Surface | Format | Spec | Reference |
|---|---|---|---|
| REST | OpenAPI 3.1 | /api/rfq-openapi.yaml | REST reference |
| WebSocket | AsyncAPI 3.0 | /api/rfq-asyncapi.yaml | WebSocket reference |
Authentication
The RFQ API uses wallet-based SIWE login to mint API credentials, then HMAC signatures on every private request. Public market-data endpoints are unauthenticated.
Login flow
- Request a single-use nonce with
POST /v1/auth/challenge. - Build a SIWE (EIP-4361) message that includes the nonce, gateway domain, URI, chain ID, and issued-at timestamp.
- Sign the SIWE message with the account wallet.
- Submit the SIWE message and wallet signature to
POST /v1/auth/api-keys. - Store the returned public
accessKeyand base64secret. - Sign each private request with the HMAC secret.
The secret is returned only once, at login, and never again. Treat it as a production secret and rotate it by minting a new key before revoking the old one.
Keep the secret out of the browser. The interactive API Reference sends public market-data requests only: login and the private operations are refused from that origin by design, since anything running on a page can read what you type into it. Sign requests from curl or your own client instead.
Whitespace and line breaks are part of the SIWE payload, so sign exactly the message you send to the API:
{domain} wants you to sign in with your Ethereum account:
{address}
Create Silhouette API key
URI: https://{domain}/
Version: 1
Chain ID: {chainId}
Nonce: {nonce}
Issued At: {issuedAt}
Signing requests
Every private request carries three headers:
| Header | Value |
|---|---|
Authorization | Bearer <access-key> |
Silhouette-API-Timestamp | Unix timestamp in milliseconds |
Silhouette-API-Signature | Base64 HMAC-SHA256 signature |
The signature is the HMAC-SHA256, under the secret, of the canonical string built from the exact request data the server receives:
{timestamp}
{METHOD}
{path}?{query}
{body}
For GET and bodyless DELETE requests the body line is empty. For query-string endpoints, such as DELETE /v1/auth/api-keys?all=true, include the query string in the signed path. Requests are accepted only when the timestamp is fresh within the gateway window, currently 30 seconds.
import base64
import hashlib
import hmac
import time
TIMESTAMP_HEADER = "Silhouette-API-Timestamp"
SIGNATURE_HEADER = "Silhouette-API-Signature"
def hmac_headers(access_key, secret_b64, method, path, body=""):
secret = base64.b64decode(secret_b64)
timestamp = str(int(time.time() * 1000))
message = "\n".join([timestamp, method.upper(), path, body]).encode()
signature = base64.b64encode(
hmac.new(secret, message, hashlib.sha256).digest()
).decode()
return {
"Authorization": f"Bearer {access_key}",
TIMESTAMP_HEADER: timestamp,
SIGNATURE_HEADER: signature,
}
For POST requests, sign the serialised JSON string you send. If the body bytes differ from the bytes used to compute the signature, the gateway rejects the request.
The shared WebSocket stream is authenticated the same way, in an in-band auth frame sent after connecting (the credential pair signs a canonical GET /v1/rfq/ws), because browsers cannot set headers on the WebSocket upgrade. The socket is authenticated once; later frames carry no signature. See the WebSocket API page for the frame protocol.
Key lifecycle
Multiple live keys can belong to the same account at the same time, which allows gradual rotation:
- Mint a new key.
- Move traffic to the new key.
- Confirm the new key can sign private requests.
- Revoke the old key with
DELETE /v1/auth/api-keys/{accessKey}.
For a sign-out-everywhere action, send DELETE /v1/auth/api-keys?all=true. The signing key used for that request is revoked too. The full set of auth endpoints is documented in the API Reference.