Jump to
Ctrl
+
/

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 {}.

Was this page helpful?
Yes
No
Powered by

On this page