Skip to main content
Skip to main content

RFQ API authentication

warning

The Silhouette REST API v1 spec is in beta and may change as RFQ workflows evolve.

The RFQ API uses wallet-based SIWE login to mint API credentials, then HMAC signatures for private REST requests. Public auth endpoints issue the challenge and credential pair. All private RFQ, maker, funding, balance, and key-management endpoints require HMAC headers.

Flow

  1. Request a single-use nonce with POST /v1/auth/challenge.
  2. Build a SIWE message that includes the nonce, gateway domain, URI, chain ID, and issued-at timestamp.
  3. Sign the SIWE message with the account wallet.
  4. Submit the SIWE message and wallet signature to POST /v1/auth/api-keys.
  5. Store the returned accessKey and base64 secret.
  6. Sign each private request with the HMAC secret.

The secret is returned only once. Treat it as a production secret and rotate it by minting a new key before revoking the old one.

Auth endpoints

MethodPathOperation IDAuthPurpose
POST/v1/auth/challengeauthChallengeNoneIssue a single-use SIWE nonce.
POST/v1/auth/api-keyssiweLoginNoneVerify SIWE and mint an HMAC credential pair.
GET/v1/auth/api-keyslistApiKeysHMACList the caller's live API keys.
DELETE/v1/auth/api-keys?all=truerevokeAllApiKeysHMACRevoke every live key for the caller.
DELETE/v1/auth/api-keys/{accessKey}revokeApiKeyHMACRevoke one key for the caller.

SIWE message

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}

Silhouette /ws session

URI: https://{domain}/
Version: 1
Chain ID: {chainId}
Nonce: {nonce}
Issued At: {issuedAt}

Submit the signed message to mint credentials:

POST /v1/auth/api-keys
Content-Type: application/json

{
"message": "<siwe-message>",
"signature": "<0x-wallet-signature>"
}

The response returns the public accessKey and base64 secret.

HMAC headers

Every private endpoint requires these headers:

HeaderValue
AuthorizationBearer <access-key>
Silhouette-API-TimestampUnix timestamp in milliseconds
Silhouette-API-SignatureBase64 HMAC-SHA256 signature

The signature is computed over the exact request data the server receives:

{timestamp}
{METHOD}
{path}?{query}
{body}

Use the exact HTTP method, path, query string, and serialized body bytes. 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 serialized JSON string you send:

import json

body = json.dumps(
{
"instrumentId": "XTSLA-USDC-SPOT",
"side": "BUY",
"baseQty": "0.5",
"quoteLimit": "1000",
"autoAccept": True,
},
separators=(",", ":"),
)

headers = hmac_headers(access_key, secret_b64, "POST", "/v1/rfq/requests", body)
headers["Content-Type"] = "application/json"

If the body bytes differ from the bytes used to compute the signature, the gateway rejects the request.

Key lifecycle

Multiple live keys can belong to the same account at the same time. This allows gradual rotation:

  1. Mint a new key.
  2. Move traffic to the new key.
  3. Confirm the new key can sign private requests.
  4. 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, so the next request signed by that key should fail with 401.

Endpoint details

Request a login challenge

POST /v1/auth/challenge

Issues a single-use nonce to embed in the SIWE login message. This endpoint is public.

StatusMeaning
200Challenge nonce returned.
500Internal server error.

Mint an HMAC credential pair

POST /v1/auth/api-keys

Verifies the SIWE message and wallet signature, then returns an accessKey and base64 secret.

FieldRequiredNotes
messageYesSIWE message containing the challenge nonce, domain, and chain ID.
signatureYesWallet signature over message, as a 0x-prefixed hex string.
expiresInSecsNoRequested credential lifetime. Omit for the default; requests above the maximum are rejected.
StatusMeaning
200Credential pair minted.
400Request validation failed.
401SIWE verification failed.
500Internal server error.

List live API keys

GET /v1/auth/api-keys

Returns live key metadata for the caller. Secrets are never returned after issuance.

StatusMeaning
200Live keys returned.
401Missing, expired, revoked, or unknown access key.
403Signature mismatch.
500Internal server error.

Revoke all API keys

DELETE /v1/auth/api-keys?all=true

Revokes every live key for the caller. The all=true query parameter is required and must be included in the signed path.

StatusMeaning
204Every live key was revoked.
400Missing or invalid all=true query parameter.
401Missing, expired, revoked, or unknown access key.
403Signature mismatch.
500Internal server error.

Revoke one API key

DELETE /v1/auth/api-keys/{accessKey}

Revokes one key owned by the caller. Use this for routine rotation or to contain a leaked credential.

StatusMeaning
204Key revoked.
401Missing, expired, revoked, or unknown signing key.
403Signature mismatch.
404Key not found for the caller.
500Internal server error.