Signed requests (crypto exchanges)
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:
- The client generates a timestamp or nonce.
- It builds a message from parts of the request, such as the method, path, query, body and timestamp.
- It signs that message with a secret key (HMAC, Ed25519, RSA or a JWT).
- 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
swy auth connect binanceEnter 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:
echo '{"api_key":"…","secret_key":"…"}' | swy auth connect binance --jsonSome 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.
| Exchange | Preset | Status |
|---|---|---|
| CoinSwitch PRO (Spot and Futures) | coinswitch | Verified |
| CoinSwitch PRO HFT and B2B | coinswitch_hft | Verified |
| Binance Spot (HMAC key) | binance | Verified |
| MEXC Spot | mexc | Verified |
| Kraken Spot | kraken | Verified |
| Bitvavo | bitvavo | Verified |
| WazirX | wazirx | Checked against docs |
| Bybit (v5, HMAC key) | bybit | Checked against docs |
| OKX (v5) | okx | Checked against docs |
| KuCoin (API key version 2+) | kucoin | Checked against docs |
| Bitget (v2, HMAC key) | bitget | Checked against docs |
| Gate (APIv4) | gate | Checked against docs |
| Delta Exchange | delta | Checked against docs |
| CoinDCX | coindcx | Checked against docs |
| Kraken Futures | kraken_futures | Checked against docs |
| Coinbase Advanced Trade (CDP key) | coinbase_advanced | Checked against docs |
| Coinbase Exchange | coinbase_exchange | Checked against docs |
| Bitfinex (v2) | bitfinex | Checked against docs |
| Bitstamp (v2) | bitstamp | Checked against docs |
| Phemex | phemex | Checked against docs |
| HTX (Huobi) Spot | htx | Checked against docs |
| Deribit | deribit | Checked against docs |
| Upbit | upbit | Checked against docs |
| Crypto.com Exchange (v1) | cryptocom | Checked against docs |
| Gemini | gemini | Checked 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
| Field | Meaning |
|---|---|
version | Recipe format version (currently 1). |
credentials | What auth connect asks for: name, label, secret (hidden input), encoding (utf8, hex, base64 or pem), and optional. |
steps | Named values, computed in order (see below). |
inject | query and body_fields added before the message is built, so they are signed (timestamp, recvWindow, nonce). position: first puts a field first. |
set | headers, query and body_fields added after signing. body: empty sends no body (Gemini). |
sensitive | Extra names to redact. Values built from a credential or a signature are redacted automatically. |
rules | Different schemes per host or path_prefix. The first match is used, and a request no rule matches is refused, never sent unsigned. |
verify | A 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,bodyorcontent_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
op | Produces |
|---|---|
time | The time: unit is ms, s, us, iso8601_ms, iso8601_s or iso8601_s_nozone. |
expires | The time plus seconds. |
nonce | monotonic_ms or monotonic_us (strictly increasing per key, across processes), uuid4, or random_hex. |
literal | A constant value. |
concat | parts joined by separator. |
hash | sha256, sha384 or sha512 of input. skip_empty gives an empty result for an empty input. |
hmac | An HMAC of input with key (a credential or another step). |
sign | An ed25519 or rsa_pkcs1_sha256 signature of input. |
jwt | A JWT (ES256, EdDSA, HS256 or HS512) from header and claims templates, with an optional lifetime. |
encode | base64, base64url, hex or urlencode of input. |
canonical_params | sorted_urlencoded or sorted_kv_concat parameters, or json_query (a JSON body as key=value&…). |
trim_prefix | input 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).