Authentication
All API requests require authentication via the x-api-key header and HMAC-SHA256 signature verification via the x-signature, x-timestamp and x-nonce headers.
Required headers
| Header | Value | Status |
|---|---|---|
x-api-key | tq_xxxxx | Required |
Content-Type | application/json | Required |
x-signature | HMAC-SHA256 hex | Required |
x-timestamp | Unix ms (e.g. 1708300000000) | Required |
x-nonce | Random, 16+ characters, new for every request (e.g. openssl rand -hex 16) | Required |
Signature formula
x-signature = HMAC-SHA256(API_SECRET, METHOD:PATH:timestamp:nonce:bodyHash)PATH is the path with its query string, exactly as sent (e.g. /agents?status=active). For GET requests, the body is signed as an empty string.
API Secret is only shown once when created. Store it securely.
Timestamp tolerance: ±5 minutes. Requests outside this window are rejected.
401 or 403? 401 means something is missing or stale: no
x-api-key, or SIGNATURE_REQUIRED, TIMESTAMP_REQUIRED, TIMESTAMP_EXPIRED, NONCE_REQUIRED, NONCE_INVALID. 403 means it was checked and refused: SIGNATURE_INVALID (a wrong secret, or a different path, query or body than you signed), an unknown, revoked or expired key, or an address outside the key's IP allowlist. The body's error or code names which.HMAC-signed request example
const crypto = require('crypto');
const API_KEY = 'tq_abc123';
const API_SECRET = 'YOUR_API_SECRET';
const BASE_URL = 'https://api.touchque.com';
function signRequest(method, path, body, timestamp, nonce) {
const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
const msg = `${method.toUpperCase()}:${path}:${timestamp}:${nonce}:${bodyHash}`;
return crypto.createHmac('sha256', API_SECRET).update(msg).digest('hex');
}
const body = JSON.stringify({ externalUsername: 'user@company.com' });
const timestamp = Date.now().toString();
const nonce = crypto.randomBytes(16).toString('hex');
const method = 'POST';
const path = '/auth/generate-secret';
const response = await axios.post(`${BASE_URL}${path}`, body, {
headers: {
'x-api-key': API_KEY,
'x-signature': signRequest(method, path, body, timestamp, nonce),
'x-timestamp': timestamp,
'x-nonce': nonce,
'Content-Type': 'application/json',
}
});import hmac, hashlib, time, json, secrets
API_KEY = "tq_abc123"
API_SECRET = "YOUR_API_SECRET"
def sign_request(method, path, body, timestamp, nonce):
body_hash = hashlib.sha256(body.encode()).hexdigest()
msg = f"{method.upper()}:{path}:{timestamp}:{nonce}:{body_hash}"
return hmac.new(API_SECRET.encode(), msg.encode(), hashlib.sha256).hexdigest()
body = json.dumps({"externalUsername": "user@company.com"})
timestamp = str(int(time.time() * 1000))
nonce = secrets.token_hex(16)
method = "POST"
path = "/auth/generate-secret"
headers = {
"x-api-key": API_KEY,
"x-signature": sign_request(method, path, body, timestamp, nonce),
"x-timestamp": timestamp,
"x-nonce": nonce,
"Content-Type": "application/json",
}BODY='{"externalUsername":"user@company.com"}'
BODY_HASH=$(echo -n "$BODY" | openssl dgst -sha256 | awk '{print $2}')
TS=$(date +%s000)
NONCE=$(openssl rand -hex 16)
MSG="POST:/auth/generate-secret:$TS:$NONCE:$BODY_HASH"
SIG=$(echo -n "$MSG" | openssl dgst -sha256 -hmac "YOUR_API_SECRET" | awk '{print $2}')
curl -X POST https://api.touchque.com/auth/generate-secret \\
-H "x-api-key: tq_abc123" \\
-H "x-signature: $SIG" \\
-H "x-timestamp: $TS" \\
-H "x-nonce: $NONCE" \\
-H "Content-Type: application/json" \\
-d "$BODY"