Skip to content

Most APIs accept a fixed credential: an API key or an OAuth token sent with every call. Crypto exchanges don’t. Each request has to be signed just before it is sent:

  1. The client generates a timestamp or nonce.
  2. It builds a message from parts of the request, such as the method, path, query, body and timestamp.
  3. It signs that message with a secret key (HMAC, Ed25519, RSA or a JWT).
  4. It sends the signature, and usually the timestamp and the public key, in headers, query parameters or the body.

The signature is different for every request, so it can’t be stored and replayed. Swytchcode handles this with the request_signature auth type: the CLI signs each request on your machine just before sending it.


What you and your agents see

  • Connect once with swy auth connect <provider>. The CLI asks for each field the exchange needs, such as API key, secret key, and passphrase if the exchange uses one. It checks them against the exchange when it can, then stores them encrypted on this machine.
  • Call tools as usual. Agents never see or provide a timestamp, nonce, signature or key; those fields are left out of tool schemas. A value passed for one anyway is ignored, with a warning.
  • Every attempt is signed fresh. A retry, or a call sent after a human approval wait, gets a new timestamp.
  • Public endpoints (server time, tickers) are sent unsigned and work before you connect.
  • Secrets stay local. The secret never leaves your machine, and signatures and keys are redacted from dry runs, verbose output and the audit log.

Connect an exchange

Terminal window
swy auth connect binance
Enter your binance API key: vmPU…
Enter your binance Secret key (hidden):
Checked the binance credentials - accepted.
Connected binance (request_signature) - stored locally, encrypted. Never leaves this machine.

A key pair the exchange rejects is never stored.

For scripts, CI and servers, pass every field as one JSON object on stdin:

Terminal window
echo '{"api_key":"…","secret_key":"…"}' | swy auth connect binance --json

Some exchanges use a private key in PEM form, such as a Coinbase key. Paste it with literal \n escapes, or paste the key file’s JSON ({"name": "...", "privateKey": "..."}) at the first prompt.

For end users of your own app, see Multi-tenant apps.

Clock drift

Exchanges reject a timestamp more than a few seconds off. swy doctor reports how far this machine’s clock is from each exchange that publishes its server time. Where the exchange supports it, the CLI also reads the exchange’s clock and corrects for the difference.


Exchange presets

Provider owners pick one of these presets in the app when they set a library’s auth type to Signed Requests (Crypto Exchanges). Presets are data on the Swytchcode backend, so adding an exchange needs no CLI update.

ExchangePresetStatus
CoinSwitch PRO (Spot and Futures)coinswitchVerified
CoinSwitch PRO HFT and B2Bcoinswitch_hftVerified
Binance Spot (HMAC key)binanceVerified
MEXC SpotmexcVerified
Kraken SpotkrakenVerified
BitvavobitvavoVerified
WazirXwazirxChecked against docs
Bybit (v5, HMAC key)bybitChecked against docs
OKX (v5)okxChecked against docs
KuCoin (API key version 2+)kucoinChecked against docs
Bitget (v2, HMAC key)bitgetChecked against docs
Gate (APIv4)gateChecked against docs
Delta ExchangedeltaChecked against docs
CoinDCXcoindcxChecked against docs
Kraken Futureskraken_futuresChecked against docs
Coinbase Advanced Trade (CDP key)coinbase_advancedChecked against docs
Coinbase Exchangecoinbase_exchangeChecked against docs
Bitfinex (v2)bitfinexChecked against docs
Bitstamp (v2)bitstampChecked against docs
PhemexphemexChecked against docs
HTX (Huobi) SpothtxChecked against docs
DeribitderibitChecked against docs
UpbitupbitChecked against docs
Crypto.com Exchange (v1)cryptocomChecked against docs
GeminigeminiChecked against docs
  • Verified: the preset reproduces the exchange’s own published example signature.
  • Checked against docs: the preset follows the exchange’s documented algorithm, but the exchange publishes no example to compare against.

CoinSwitch HFT and B2B is one provider with two key pairs. B2B master endpoints (/dma/api/v1/master/…) are signed with the optional master key pair, and everything else with your main or subaccount key pair.


For spec authors: x-swytchcode-signing

A spec can declare its own signing recipe, so a library uploaded from it works without picking a preset. Put the recipe on the security scheme that carries the signature, or at the spec root to cover every operation that declares security:

This is Binance’s: an HMAC-SHA256 of the query string and body, sent as a signature query parameter, with timestamp and recvWindow added first so they are signed too.

components:
securitySchemes:
BinanceSignature:
type: apiKey
in: header
name: X-MBX-APIKEY
x-swytchcode-signing:
version: 1
credentials:
- {name: api_key, label: API key}
- {name: secret_key, label: Secret key, secret: true}
steps:
- {id: ts, op: time, unit: ms}
- {id: msg, op: concat, parts: ['{query}', '{body}']}
- {id: sig, op: hmac, alg: sha256, key: secret_key, input: '{msg}', output: hex}
inject:
query:
- {name: recvWindow, value: '5000'}
- {name: timestamp, value: '{ts}'}
set:
headers:
X-MBX-APIKEY: '{api_key}'
query:
- {name: signature, value: '{sig}'}
verify: {method: GET, path: /api/v3/account}
time_sync: {path: /api/v3/time, json_path: serverTime, unit: ms, on_error: [{status: 400, body_contains: '-1021'}]}

When the spec is converted (Wrekenfile 2.1.0), the recipe goes into the Wrekenfile’s SIGNING block, and methods that use it get SECURITY: request_signature. Every header and parameter the recipe produces is left out of the method’s inputs, so Binance’s required timestamp and signature parameters are dropped. Operations with security: [] stay unsigned.

You can also pin a method’s name with x-swytchcode-canonical-id on the operation, which is useful when an exchange has Spot and Futures endpoints that would otherwise get similar names.

A recipe chosen in the app takes precedence over the spec’s. If a later upload declares a different recipe, the app shows a notice instead of replacing it.

Recipe fields

FieldMeaning
versionRecipe format version (currently 1).
credentialsWhat auth connect asks for: name, label, secret (hidden input), encoding (utf8, hex, base64 or pem), and optional.
stepsNamed values, computed in order (see below).
injectquery and body_fields added before the message is built, so they are signed (timestamp, recvWindow, nonce). position: first puts a field first.
setheaders, query and body_fields added after signing. body: empty sends no body (Gemini).
sensitiveExtra names to redact. Values built from a credential or a signature are redacted automatically.
rulesDifferent schemes per host or path_prefix. The first match is used, and a request no rule matches is refused, never sent unsigned.
verifyA read-only {method, path} that auth connect calls to check new credentials.
time_sync{path, json_path, unit} of the exchange’s server time. Add on_error to re-sync and retry once after a timestamp rejection, or proactive: true to sync before signing.

Templates

Values are text with {name} placeholders. A placeholder can name:

  • a credential;
  • a step id;
  • a request input: method, method_lower, host, path, query, query_decoded, query_sorted, path_query, path_query_decoded, body or content_type.

query and body are the exact bytes sent. path_query_decoded is the path and query with URL escapes decoded and + read as a space.

Steps

opProduces
timeThe time: unit is ms, s, us, iso8601_ms, iso8601_s or iso8601_s_nozone.
expiresThe time plus seconds.
noncemonotonic_ms or monotonic_us (strictly increasing per key, across processes), uuid4, or random_hex.
literalA constant value.
concatparts joined by separator.
hashsha256, sha384 or sha512 of input. skip_empty gives an empty result for an empty input.
hmacAn HMAC of input with key (a credential or another step).
signAn ed25519 or rsa_pkcs1_sha256 signature of input.
jwtA JWT (ES256, EdDSA, HS256 or HS512) from header and claims templates, with an optional lifetime.
encodebase64, base64url, hex or urlencode of input.
canonical_paramssorted_urlencoded or sorted_kv_concat parameters, or json_query (a JSON body as key=value&…).
trim_prefixinput without prefix.

hash, hmac and sign take an output of hex (default), hex_upper, base64, base64url or raw.

Recipes can only combine these building blocks; there is no expression language. A recipe that uses anything the CLI doesn’t know is rejected with “upgrade the CLI”, and is never run partially.

See also: the Wreken specification (section 15).