Skip to content

Authentication

Every /v1/* request carries three headers:

Header Value
x-client-id your client id
x-timestamp current unix time in seconds (±300 s of server time)
x-signature hex HMAC-SHA256 of the message below, keyed with your secret
message = client_id + timestamp + METHOD + path + raw_body
signature = hex( hmac_sha256( secret, message ) )
  • METHOD is uppercase. path is the URL path only — no host, no query string. URL-encode path segments before signing, exactly as sent.
  • raw_body is the exact bytes you send (empty for GET). Serialize JSON once and sign those bytes; re-serializing can reorder keys and break the signature.
  • The query string is not signed; it is protected by TLS.
import hashlib, hmac, json, time, urllib.request
BASE, CID, SECRET = "https://api.oldsteelarsenal.com", "my-client", "…"
def call(method: str, path: str, payload: dict | None = None):
body = b"" if payload is None else json.dumps(payload).encode()
ts = str(int(time.time()))
sig = hmac.new(SECRET.encode(), f"{CID}{ts}{method}{path}".encode() + body,
hashlib.sha256).hexdigest()
req = urllib.request.Request(BASE + path, data=body or None, method=method,
headers={"x-client-id": CID, "x-timestamp": ts, "x-signature": sig,
"content-type": "application/json"})
with urllib.request.urlopen(req, timeout=20) as r:
return json.load(r)
call("POST", "/v1/ffl/search", {"zip": "76648", "radius_miles": 25})
import { createHmac } from 'node:crypto'
const BASE = 'https://api.oldsteelarsenal.com', CID = 'my-client', SECRET = '…'
export async function call(method, path, payload) {
const body = payload === undefined ? '' : JSON.stringify(payload)
const ts = String(Math.floor(Date.now() / 1000))
const sig = createHmac('sha256', SECRET).update(CID + ts + method + path + body).digest('hex')
const r = await fetch(BASE + path, {
method, body: body || undefined,
headers: { 'x-client-id': CID, 'x-timestamp': ts, 'x-signature': sig, 'content-type': 'application/json' },
})
if (!r.ok) throw new Error(`${r.status} ${await r.text()}`)
return r.json()
}

When we rotate your secret, the previous one keeps working for 24 hours so you can deploy the new one without downtime.