Vaultody API Authentication
Vaultody REST API uses HMAC-SHA256 (Hash-based Message Authentication Code) to authenticate and authorize every request. You must sign each API call to verify your identity and ensure requests have not been tampered with in transit.
Overview
To interact with the Vaultody API securely, you need to:
Generate an API key pair (Key, Secret, Passphrase) in the Dashboard. Sign each request using the HMAC-SHA256 algorithm. Include four required headers with every request.
Required Credentials
Generate your credentials in the Dashboard under Developers → API Keys → Create New API Key.
API Key (x-api-key) — the public identifier of your application. Safe to log. API Secret — used to compute the signature. Never share or expose this value. Passphrase (x-api-passphrase) — an additional user-defined secret for added security.
Security: Store your API Secret and Passphrase in environment variables or a secrets manager. Never commit them to source code or include them in client-side applications.
Signature Generation
The x-api-sign header is generated by signing a message string with your API Secret using HMAC-SHA256. Step 1 — Build the message string Concatenate these five elements in order with no separator:
{timestamp}{HTTP_METHOD}{request_path}{body}{query_params}
Rules:
timestamp — current UNIX timestamp in seconds (integer as string)
HTTP_METHOD — uppercase: GET, POST, PUT
request_path — the URL path only, starting with / (e.g. /vaults/main)
body — the raw JSON request body string. Use {} for GET requests and requests with no body
query_params — query parameters as a JSON object string. Use {} if none
Examples:
GET /vaults/main with no body and no query params:
1715709672GET/vaults/main{}{}
POST/vaults/{vaultId}/vault-account with body:
{
"context": "yourExampleString",
"data": {
"item": {
"color": "#00C7E6",
"isHiddenInDashboard": false,
"name": "User Alice"
}
}
}
Important: The JSON body must be minified (no extra whitespace or newlines) before signing. Whitespace differences will cause signature mismatch.
Step 2 — Decode the API Secret Your API Secret is Base64-encoded. Decode it to raw bytes before signing: pythondecoded_secret = base64.b64decode(api_secret) Step 3 — Compute the HMAC-SHA256 signature pythonsignature = hmac.new(decoded_secret, message.encode('utf-8'), hashlib.sha256).digest() Step 4 — Base64-encode the signature pythonsignature_b64 = base64.b64encode(signature).decode() This final value is your x-api-sign header.
Required Headers
Include all four headers on every request: HeaderDescriptionx-api-keyYour public API keyx-api-signBase64-encoded HMAC-SHA256 signaturex-api-timestampCurrent UNIX timestamp in secondsx-api-passphraseYour passphraseContent-TypeMust be application/json on all requests
Code Examples
Python
pythonimport time, hmac, hashlib, base64, requests, json
api_key = 'your_api_key'
api_secret = 'your_api_secret'
passphrase = 'your_passphrase'
def signed_request(method, path, body=None, params=None):
timestamp = str(int(time.time()))
body_str = json.dumps(body, separators=(',', ':')) if body else '{}'
query_str = json.dumps(params, separators=(',', ':')) if params else '{}'
message = timestamp + method.upper() + path + body_str + query_str
decoded_secret = base64.b64decode(api_secret)
signature = hmac.new(decoded_secret, message.encode('utf-8'), hashlib.sha256).digest()
signature_b64 = base64.b64encode(signature).decode()
headers = {
'x-api-key': api_key,
'x-api-sign': signature_b64,
'x-api-timestamp': timestamp,
'x-api-passphrase': passphrase,
'Content-Type': 'application/json'
}
url = 'https://rest.vaultody.com' + path
if method.upper() == 'GET':
return requests.get(url, headers=headers, params=params)
elif method.upper() == 'POST':
return requests.post(url, headers=headers, json=body)
elif method.upper() == 'PUT':
return requests.put(url, headers=headers, json=body)
Example: list vaults
response = signed_request('GET', '/vaults/main')
print(response.json())
Node.js
javascriptconst crypto = require('crypto');
const axios = require('axios');
const apiKey = 'your_api_key';
const apiSecret = 'your_api_secret';
const passphrase = 'your_passphrase';
function signedRequest(method, path, body = null, params = null) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const bodyStr = body ? JSON.stringify(body) : '{}';
const queryStr = params ? JSON.stringify(params) : '{}';
const message = timestamp + method.toUpperCase() + path + bodyStr + queryStr;
const hmac = crypto.createHmac('sha256', Buffer.from(apiSecret, 'base64'));
hmac.update(message);
const signature = hmac.digest('base64');
const headers = {
'x-api-key': apiKey,
'x-api-sign': signature,
'x-api-timestamp': timestamp,
'x-api-passphrase': passphrase,
'Content-Type': 'application/json',
};
const url = 'https://rest.vaultody.com' + path;
const config = { headers, params };
if (method.toUpperCase() === 'GET') return axios.get(url, config);
if (method.toUpperCase() === 'POST') return axios.post(url, body, { headers });
if (method.toUpperCase() === 'PUT') return axios.put(url, body, { headers });
}
// Example: list vaults
signedRequest('GET', '/vaults/main')
.then(res => console.log(res.data))
.catch(err => console.error(err.response.data));
PHP
php<?php
$api_key = 'your_api_key';
$api_secret = 'your_api_secret';
$passphrase = 'your_passphrase';
function signed_request(string $method, string $path, array $body = [], array $params = []): array {
global $api_key, $api_secret, $passphrase;
$timestamp = (string) time();
$body_str = empty($body) ? '{}' : json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$query_str = empty($params) ? '{}' : json_encode($params, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$message = $timestamp . strtoupper($method) . $path . $body_str . $query_str;
$decoded_secret = base64_decode($api_secret);
$signature = base64_encode(hash_hmac('sha256', $message, $decoded_secret, true));
$headers = [
'x-api-key: ' . $api_key,
'x-api-sign: ' . $signature,
'x-api-timestamp: ' . $timestamp,
'x-api-passphrase: ' . $passphrase,
'Content-Type: application/json',
];
$url = 'https://rest.vaultody.com' . $path;
if (!empty($params)) $url .= '?' . http_build_query($params);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
if (strtoupper($method) === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body_str);
} elseif (strtoupper($method) === 'PUT') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_POSTFIELDS, $body_str);
}
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// Example: list vaults
$result = signed_request('GET', '/vaults/main');
print_r($result);
Postman (Pre-request Script)
javascriptvar CryptoJS = require("crypto-js");
var secretKey = pm.variables.get("x-api-key");
var passphrase = pm.variables.get("x-api-passphrase");
var secret = pm.variables.get("x-api-secret");
var timestamp = Math.floor(Date.now() / 1000).toString();
var bodyStr = (pm.request.method === 'GET')
? JSON.stringify({})
: pm.request.body.raw.replace(/\n(?=(?:[^"]*"[^"]*")*[^"]*$)/g, '').replace(/(\".*?\"|\s+)/g, '$1');
var queryObj = pm.request.url.query.reduce((acc, item) => {
acc[item.key] = item.value;
return acc;
}, {});
var messageToSign = timestamp + pm.request.method.toUpperCase()
+ pm.request.url.getPath()
+ bodyStr
+ JSON.stringify(queryObj);
var key = CryptoJS.enc.Base64.parse(secret);
var signature = CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA256(messageToSign, key));
pm.request.headers.add({ key: "x-api-timestamp", value: timestamp });
pm.request.headers.add({ key: "x-api-sign", value: signature });
pm.request.headers.add({ key: "x-api-key", value: secretKey.toString() });
pm.request.headers.add({ key: "x-api-passphrase", value: passphrase });
pm.request.headers.add({ key: "Content-Type", value: "application/json" });
Security Best Practices
Never expose your API Secret — treat it like a password. Store it in environment variables. Use IP whitelisting — restrict API key access to known server IP addresses in the Dashboard. Rotate keys regularly — regenerate API keys periodically and immediately if you suspect a compromise. Scope your keys — create separate API keys with minimal required permissions for each service. Generate a fresh timestamp per request — never reuse or cache a timestamp across calls.
Troubleshooting
| HTTP Code | Error Message | Cause | Fix |
|---|---|---|---|
| 401 | missing_api_key |
x-api-key header not sent |
Add all four auth headers to every request |
| 401 | invalid_api_key |
Wrong, expired, or deleted API key | Regenerate a new key in the Dashboard |
| 401 | invalid_api_key |
Signature mismatch | Check timestamp freshness, body minification, and secret decoding |
| 403 | endpoint_not_allowed_for_api_key |
Key lacks permission for this endpoint | Create a key with the correct scopes |
| 403 | endpoint_not_allowed_for_api_key |
Key lacks permission for this endpoint | Upgrade your plan |
| 400 | invalid_request_body_structure |
Body not wrapped in { data: { item: {} } } |
Wrap all POST/PUT bodies correctly |
| 415 | unsupported_media_type |
Missing Content-Type: application/json |
Add the Content-Type header |
Common Signature Failure Causes
Timestamp drift — the most frequent cause of 401 errors. Your server clock must be within ~30 seconds of Vaultody's server time. Always call time() / Date.now() inside your signing function, never outside it.
Body whitespace — extra spaces or newlines in the JSON body will produce a different signature than the server expects. Minify your body string before signing (use separators=(',', ':') in Python, JSON.stringify in JS).
Wrong query format — query parameters must be serialised as a JSON object string ({"key":"value"}), not a URL query string (key=value). If there are no query parameters, use {}.