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
- Learn about API Conventions
- Create a Service Account
- Explore API Resources