Authentication
Register a signer, manage API keys, and sign OBSDN API requests
OBSDN authentication has three pieces, each with a distinct job:
- Sender wallet - owns your funds, positions, and account. You sign with it for actions that move funds (withdraw, transfer) and to register a signer. Verified on-chain by the
Obsdncontract. - Signer wallet - a separate wallet authorized to sign orders on the sender's behalf, verified on-chain by the
Obsdncontract at settlement. Holds no funds. - API key pair -
api_key, a public identifier sent with every request, plusapi_secret, the confidential half used to sign requests. Verified off-chain by the OBSDN backend; has no on-chain effect.
You register a signer once. From then on, API keys issued for that signer carry the authority to act for the sender, so trading happens through API credentials rather than a raw private key on every request.
Register a Signer
The first step authorizes a signer wallet and issues the account's first API key pair in one call. Both the sender and the signer sign EIP-712 typed data to prove ownership and consent, then a single POST /auth/signers call posts both signatures together.
See the Register Signer reference for the request schema, Getting Started for an end-to-end curl example, and the Signing Guide for the EIP-712 payloads.
The response (under data) returns the first API key pair:
{
"api_key": {
"api_key": "a1b2c3d4e5f6a7b8a1b2c3d4e5f6a7b8",
"api_secret": "a1b2c3d4e5f6a7b8a1b2c3d4e5f6a7b8a1b2c3d4e5f6a7b8a1b2c3d4e5f6a7b8",
"nm": "My Trading Bot",
"sndr": "0x1234...",
"signer": "0xabcd...",
"crt_ts": "1734000000000000000",
"exp_ts": "1734604800000000000",
"is_ro": false
}
}Timestamps (crt_ts, exp_ts) are Unix nanoseconds.
Save your api_secret immediately. It is returned only once, in the response that creates the key; list endpoints
never return it.
API Key Types
A key's scope is fixed at creation by the is_ro (read-only) flag and cannot be changed afterward.
| Type | is_ro | Place / cancel orders | Read account data |
|---|---|---|---|
| Full-access | false | Yes | Yes |
| Read-only | true | No | Yes |
Read-only keys cannot place or cancel orders, modify positions or leverage, transfer or withdraw funds, or create/delete keys or register signers. Use them anywhere you need data but never need to trade: dashboards, monitoring, analytics, and backtests. Reserve full-access keys for the services that actually submit orders. Scoping by purpose limits the blast radius if a key leaks.
Managing Keys
All three endpoints require the signed headers from Signing REST Requests.
Create
Once a signer is registered, mint more key pairs for it without re-registering: one per service or environment, or to stage a rotation. Each new key inherits the signer's authority.
POST /auth/api-keys{ "nm": "Read-Only Dashboard", "is_ro": true }Both fields are optional; is_ro defaults to false. The response matches POST /auth/signers: { "api_key": { ... } } with a fresh api_key and api_secret. Save the secret immediately. See Create an additional API key.
IP Whitelisting
Lock a key to specific client IPs at creation by passing ip_whitelist:
{ "nm": "Prod Bot", "ip_whitelist": ["203.0.113.10", "198.51.100.5"] }- Up to 10 IPv4 addresses per key.
- Empty list (default) allows any IP.
- Set once at creation; to change, delete the key and create a new one.
- The server checks the IP reported by Cloudflare (
CF-Connecting-IP), which cannot be spoofed by the caller. - Use
GET /clientto see the exact IP the server observes for your connection.
Requests from an IP not in the whitelist receive 403 client IP <ip> not allowed for this API key.
List
GET /auth/api-keysReturns every key for the authenticated account. Each record includes its name (nm), signer, sender (sndr), creation and expiry timestamps (crt_ts, exp_ts), last write timestamp (wrt_ts), read-only flag (is_ro), and ip_whitelist. The api_secret is always empty here. See List API keys.
wrt_ts is the timestamp of the most recent write operation made with the key, letting you tell an idle key from an active one. It is recorded once a minute, so treat it as accurate to about a minute rather than as an exact time, and it is 0 for a key that has never placed an order. Only order placements count as writes: read requests never update it.
Delete
DELETE /auth/api-keys{ "api_keys": ["a1b2c3d4e5f6a7b8a1b2c3d4e5f6a7b8"] }Deletion is immediate and irrevocable; the keys can no longer authenticate any request. See Delete API keys.
Expiry and Rotation
| Key type | Initial TTL | Auto-extend trigger | Extension |
|---|---|---|---|
| Full-access | 7 days | Any order placement | 7 days from the order |
| Read-only | 10 years | None | N/A |
Each time a full-access key places an order (regular, conditional, or group), its expiry extends to 7 days from the order timestamp, so an actively trading key stays alive indefinitely with no manual renewal.
Auto-extend happens on order placement only. A full-access key that authenticates read requests but never places
orders expires after 7 days. Track exp_ts for keys that trade infrequently.
There is no edit operation for a key's scope or secret. To rotate, create the replacement before deleting the old one:
- Create a new key pair for the signer.
- Deploy the new credentials and confirm they work.
- Delete the old key once nothing depends on it.
The old key stays valid through steps 1-2, so there is no downtime. Rotate immediately whenever a key may have been exposed: a leaked log, a shared screenshot, a departed teammate, or a compromised host.
Sub-Account Keys
Sub-accounts register their own signers and carry their own API keys, separate from the parent account's credentials. Each sub-account's trading authority is isolated: a key issued for one sub-account cannot act on another or on the parent.
Signing REST Requests
Every protected REST endpoint requires three headers:
| Header | Value |
|---|---|
x-api-key | API key identifier (api_key) |
x-api-timestamp | Current Unix time in seconds (string) |
x-api-signature | base64(HMAC-SHA256(api_secret, timestamp + method + path + body)) |
The prehash concatenates the timestamp, the uppercase HTTP method, the URL path (no scheme, host, or query string), and the raw request body. The API secret is the HMAC key and is never sent over the wire.
Requests with a timestamp more than 5 seconds off the server clock are silently treated as unauthenticated, and the endpoint returns 401 authentication required. Regenerate the signature for every call.
TS=$(date +%s)
METHOD=GET
URL_PATH=/portfolio
BODY=""
SIG=$(printf '%s%s%s%s' "$TS" "$METHOD" "$URL_PATH" "$BODY" \
| openssl dgst -sha256 -hmac "$API_SECRET" -binary \
| base64)
curl "https://api.obsdn.trade$URL_PATH" \
-H "x-api-key: $API_KEY" \
-H "x-api-timestamp: $TS" \
-H "x-api-signature: $SIG"For requests with a JSON body, pre-serialize the body once and reuse the same string in both the prehash and the request. Whitespace or key-order differences between the signed and sent bytes invalidate the signature.
Authenticating WebSocket
Private WebSocket channels (order, position, portfolio) require an auth message before subscription. The signature uses the same api_key / api_secret, but the prehash is ${api_key},${timestamp} and the timestamp window is ±60 seconds. See WebSocket Overview for the full recipe.
Security Best Practices
- Guard the secret. Shown once at creation. Store it in a secrets manager, never in source control, client-side code, or logs.
- Scope to least privilege. Use read-only keys wherever trading is not required.
- One key per service. Separate credentials let you revoke a single compromised service without disrupting the others.
- Use separate signer wallets. If a signer key leaks, only signing authority is exposed; funds stay under the sender wallet, which can revoke the signer.
- Rotate on exposure. No scheduled rotation is needed, since active full-access keys auto-renew.
- Delete unused keys. Every live key is attack surface.
- Pin production keys to known IPs. Use
ip_whiteliston keys that only run from fixed infrastructure; a leaked key is useless from any other address.
Error Codes
| Code | HTTP | Message | Fix |
|---|---|---|---|
E0001 | 401 | authentication required | Missing/invalid x-api-key, x-api-signature, or x-api-timestamp; or timestamp more than 5 s off the server clock |
E0006 | 403 | this operation requires a full API key | Use a full-access (non-read-only) key |
E0005 | 401 | invalid signature | For orders/withdrawals, verify the EIP-712 payload (see Signing); for the request HMAC, verify the base64 digest and prehash format (timestamp + METHOD + path + body) |
| 403 | client IP <ip> not allowed for this API key | Request came from an IP not in the key's ip_whitelist. Use GET /client to check what IP the server sees, or delete and recreate the key with the correct list. |
Rate-limit responses (429, codes E0501 / E0502) are retryable. Back off with exponential delay and jitter; see Error Handling.