Skip to main content

Authentication

How to authenticate with the PDaaS API using HMAC signatures.

Overview

:class: warning
**Users cannot access the API directly.** All API access requires a service account with HMAC authentication.

PDaaS uses HMAC-SHA256 signatures for API authentication to ensure:

  • Request integrity (body cannot be tampered)
  • Request authenticity (only holder of secret key can sign)
  • Replay protection (nonce prevents reuse)

Getting API Access

1. Create a Service Account

Service accounts are for programmatic API access. There are two types:

Personal Service Accounts:

  • Created by individual users
  • Maximum 2 active per user
  • For personal scripts, CLI tools, development

Organization Service Accounts:

  • Created by admins
  • For CI/CD, automation, shared access

See Service Accounts Guide for details.

2. Issue an API Key

Once you have a service account, issue an API key:

POST /service-accounts/{sa_id}/keys
{
"description": "Production deployment key",
"expires_at": "2026-12-31T23:59:59Z",
"ip_allowlist": ["192.168.1.0/24"]
}

Response:

{
"access_key": "sa_mycomp_acc123_x7y8z9...",
"secret_key": "sk_live_abc123...", // ONLY SHOWN ONCE!
"expires_at": "2026-12-31T23:59:59Z"
}
:class: warning
The **secret_key** is only shown ONCE during creation. Store it securely - it cannot be retrieved later.

HMAC Authentication

Required Headers

Every API request must include:

Authorization: HMAC {access_key}:{signature}
x-date: 2025-09-30T12:00:00Z
x-nonce: unique-request-identifier
x-content-sha256: sha256-hash-of-body

Signature Generation

The signature is computed as:

signature = Base64(HMAC-SHA256(
secret_key,
string_to_sign
))

string_to_sign =
HTTP_METHOD + "\n" +
PATH + "\n" +
QUERY_STRING + "\n" +
x-date + "\n" +
x-nonce + "\n" +
x-content-sha256

Example: GET Request

import hmac
import hashlib
import base64
from datetime import datetime
import uuid

# Your credentials
access_key = "sa_mycomp_acc123_x7y8z9..."
secret_key = "sk_live_abc123..."

# Request details
method = "GET"
path = "/accounts"
query_string = "page=1&quantity=20"
date = datetime.utcnow().isoformat() + "Z"
nonce = str(uuid.uuid4())
body = "" # Empty for GET
content_sha256 = hashlib.sha256(body.encode()).hexdigest()

# Build string to sign
string_to_sign = f"{method}\n{path}\n{query_string}\n{date}\n{nonce}\n{content_sha256}"

# Generate signature
signature = base64.b64encode(
hmac.new(
secret_key.encode(),
string_to_sign.encode(),
hashlib.sha256
).digest()
).decode()

# Make request
headers = {
"Authorization": f"HMAC {access_key}:{signature}",
"x-date": date,
"x-nonce": nonce,
"x-content-sha256": content_sha256
}

Example: POST Request

import json

# Request details
method = "POST"
path = "/accounts"
query_string = ""
date = datetime.utcnow().isoformat() + "Z"
nonce = str(uuid.uuid4())
body = json.dumps({
"name": "New Account",
"is_default": False
})
content_sha256 = hashlib.sha256(body.encode()).hexdigest()

# Build string to sign
string_to_sign = f"{method}\n{path}\n{query_string}\n{date}\n{nonce}\n{content_sha256}"

# Generate signature (same as above)
signature = base64.b64encode(
hmac.new(
secret_key.encode(),
string_to_sign.encode(),
hashlib.sha256
).digest()
).decode()

# Make request
headers = {
"Authorization": f"HMAC {access_key}:{signature}",
"x-date": date,
"x-nonce": nonce,
"x-content-sha256": content_sha256,
"Content-Type": "application/json"
}

Complete Examples

Python

import requests
import hmac
import hashlib
import base64
from datetime import datetime
import uuid
import json

class PDaaSClient:
def __init__(self, base_url, access_key, secret_key):
self.base_url = base_url
self.access_key = access_key
self.secret_key = secret_key

def _sign_request(self, method, path, query_string="", body=""):
"""Generate HMAC signature for request"""
date = datetime.utcnow().isoformat() + "Z"
nonce = str(uuid.uuid4())
content_sha256 = hashlib.sha256(body.encode()).hexdigest()

string_to_sign = (
f"{method}\n{path}\n{query_string}\n"
f"{date}\n{nonce}\n{content_sha256}"
)

signature = base64.b64encode(
hmac.new(
self.secret_key.encode(),
string_to_sign.encode(),
hashlib.sha256
).digest()
).decode()

return {
"Authorization": f"HMAC {self.access_key}:{signature}",
"x-date": date,
"x-nonce": nonce,
"x-content-sha256": content_sha256
}

def get(self, path, params=None):
"""Make GET request"""
query_string = "&".join(
f"{k}={v}" for k, v in (params or {}).items()
)
headers = self._sign_request("GET", path, query_string)

url = f"{self.base_url}{path}"
if query_string:
url += f"?{query_string}"

return requests.get(url, headers=headers)

def post(self, path, data):
"""Make POST request"""
body = json.dumps(data)
headers = self._sign_request("POST", path, body=body)
headers["Content-Type"] = "application/json"

return requests.post(
f"{self.base_url}{path}",
headers=headers,
data=body
)

# Usage
client = PDaaSClient(
base_url="https://api.console.solucao42.com.br",
access_key="sa_mycomp_acc123_x7y8z9...",
secret_key="sk_live_abc123..."
)

# List accounts
response = client.get("/accounts", params={"page": 1, "quantity": 20})
print(response.json())

# Create account
response = client.post("/accounts", data={
"name": "New Account",
"is_default": False
})
print(response.json())

Node.js

const crypto = require('crypto');
const axios = require('axios');
const { v4: uuidv4 } = require('uuid');

class PDaaSClient {
constructor(baseUrl, accessKey, secretKey) {
this.baseUrl = baseUrl;
this.accessKey = accessKey;
this.secretKey = secretKey;
}

_signRequest(method, path, queryString = '', body = '') {
const date = new Date().toISOString();
const nonce = uuidv4();
const contentSha256 = crypto
.createHash('sha256')
.update(body)
.digest('hex');

const stringToSign = [
method,
path,
queryString,
date,
nonce,
contentSha256
].join('\n');

const signature = crypto
.createHmac('sha256', this.secretKey)
.update(stringToSign)
.digest('base64');

return {
'Authorization': `HMAC ${this.accessKey}:${signature}`,
'x-date': date,
'x-nonce': nonce,
'x-content-sha256': contentSha256
};
}

async get(path, params = {}) {
const queryString = Object.entries(params)
.map(([k, v]) => `${k}=${v}`)
.join('&');

const headers = this._signRequest('GET', path, queryString);

let url = `${this.baseUrl}${path}`;
if (queryString) url += `?${queryString}`;

return axios.get(url, { headers });
}

async post(path, data) {
const body = JSON.stringify(data);
const headers = {
...this._signRequest('POST', path, '', body),
'Content-Type': 'application/json'
};

return axios.post(`${this.baseUrl}${path}`, body, { headers });
}
}

// Usage
const client = new PDaaSClient(
'https://api.console.solucao42.com.br',
'sa_mycomp_acc123_x7y8z9...',
'sk_live_abc123...'
);

// List accounts
client.get('/accounts', { page: 1, quantity: 20 })
.then(response => console.log(response.data));

// Create account
client.post('/accounts', {
name: 'New Account',
is_default: false
}).then(response => console.log(response.data));

Security Considerations

Time Window

Requests are valid for 5 minutes from the x-date timestamp. This prevents replay attacks with old signatures.

Nonce Tracking

The x-nonce must be unique per request. The server tracks nonces for 5 minutes to prevent replay attacks.

Body Integrity

The x-content-sha256 header ensures the request body hasn't been tampered with in transit.

Key Rotation

Rotate API keys every 90 days for security best practices. Issue a new key before revoking the old one to avoid downtime.

IP Allowlists

Restrict API keys to specific IP ranges for additional security:

{
"ip_allowlist": ["192.168.1.0/24", "10.0.0.0/8"]
}

Error Responses

Invalid Signature

401 Unauthorized
{
"error": "InvalidSignature",
"message": "The provided signature does not match"
}

Expired Request

401 Unauthorized
{
"error": "RequestExpired",
"message": "Request timestamp is outside the 5-minute window"
}

Replay Attack Detected

401 Unauthorized
{
"error": "NonceAlreadyUsed",
"message": "This nonce has already been used"
}

Revoked API Key

401 Unauthorized
{
"error": "ApiKeyRevoked",
"message": "This API key has been revoked"
}

Next Steps