Authentication & Security
This guide covers all authentication methods supported by the Piaxis API, security best practices, and implementation examples for different integration scenarios.
Overview
Piaxis supports multiple authentication methods depending on your integration needs:
-
API Key Authentication: For server-to-server integrations
-
OAuth2 Flow: For applications acting on behalf of users
-
Merchant Authorization: For escrow and disbursement operations
-
OTP Verification: For sensitive operations requiring user confirmation
Authentication Methods
API Key Authentication
The simplest authentication method for server-to-server integrations. Your API key provides full access to your merchant account.
Headers Required:
api-key: YOUR_MERCHANT_API_KEY
Content-Type: application/json
Example Request:
POST /api/payments/create HTTP/1.1
Host: api.gopiaxis.com
api-key: pk_live_1234567890abcdef
Content-Type: application/json
{
"amount": 5000,
"currency": "UGX",
"payment_method": "mtn"
}
Security Notes: - Keep your API key secure and never expose it in client-side code - Use environment variables to store API keys - Rotate API keys regularly through your merchant dashboard
OAuth2 Flow
OAuth2 is required for applications that act on behalf of users, particularly for Piaxis External payments and user account operations.
Step 1: Authorization Request
- GET /api/authorize
-
Redirect users to start the OAuth2 authorization flow.
- Query Parameters:
-
-
merchant_id – Your merchant ID (UUID format)
-
redirect_uri – Your callback URL (must be registered in your merchant dashboard)
-
response_type – Must be “code”
-
scope – Requested permissions (space-separated)
-
state – Optional security parameter to prevent CSRF attacks
-
Available Scopes: -
payments:create- Create payments -payments:read- Read payment information -escrow:create- Create escrow transactions -escrow:manage- Manage escrow operations -profile:read- Read user profile informationExample Authorization URL:
GET /api/authorize?merchant_id=096b723a-45c5-4957-94d7-747835136265&redirect_uri=https://yourapp.com/callback&response_type=code&scope=payments:create%20escrow:create&state=abc123 HTTP/1.1 Host: api.gopiaxis.com
Step 2: Handle Authorization Callback
After user approval, Piaxis redirects to your callback URL with an authorization code:
GET /callback?code=AUTH_CODE_HERE&state=abc123 HTTP/1.1
Host: yourapp.com
Step 3: Exchange Code for Access Token
- POST /api/oauth/token
-
Exchange authorization code for access token.
- Form Parameters:
-
-
grant_type – Must be “authorization_code”
-
code – Authorization code from callback
-
redirect_uri – Same redirect URI used in authorization request
-
client_id – Your merchant ID
-
Example Request:
POST /api/oauth/token HTTP/1.1 Host: api.gopiaxis.com Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=AUTH_CODE_HERE&redirect_uri=https://yourapp.com/callback&client_id=096b723a-45c5-4957-94d7-747835136265
Example Response:
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "scope": "payments:create escrow:create" }
Step 4: Use Access Token
Include the access token in API requests:
POST /api/payments/create HTTP/1.1
Host: api.gopiaxis.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
Token Refresh
- POST /api/oauth/refresh
-
Refresh expired access token using refresh token.
- Form Parameters:
-
-
grant_type – Must be “refresh_token”
-
refresh_token – Your refresh token
-
client_id – Your merchant ID
-
Example Request:
POST /api/oauth/refresh HTTP/1.1 Host: api.gopiaxis.com Content-Type: application/x-www-form-urlencoded grant_type=refresh_token&refresh_token=REFRESH_TOKEN_HERE&client_id=096b723a-45c5-4957-94d7-747835136265
OTP Verification Flow
Certain sensitive operations require OTP (One-Time Password) verification for additional security.
Operations Requiring OTP: - Large escrow releases (configurable threshold) - Bulk disbursements - Account settings changes - High-value transactions
Step 1: Request OTP
- POST /api/auth/request-otp
-
Request OTP for sensitive operation.
Example Request:
POST /api/auth/request-otp HTTP/1.1 Host: api.gopiaxis.com api-key: YOUR_API_KEY Content-Type: application/json { "operation": "escrow_release", "escrow_id": "e123e4567-e89b-12d3-a456-426614174000", "phone_number": "+256700000000" }
Response:
{ "status": "success", "message": "OTP sent successfully", "otp_id": "otp_1234567890", "expires_in": 300 }
Step 2: Verify OTP
- POST /api/auth/verify-otp
-
Verify the OTP code.
Example Request:
POST /api/auth/verify-otp HTTP/1.1 Host: api.gopiaxis.com api-key: YOUR_API_KEY Content-Type: application/json { "otp_id": "otp_1234567890", "otp_code": "123456" }
Response:
{ "status": "success", "verification_token": "verify_token_here", "expires_in": 600 }
Step 3: Use Verification Token
Include the verification token in your sensitive operation:
POST /api/escrow/release HTTP/1.1
Host: api.gopiaxis.com
api-key: YOUR_API_KEY
X-Verification-Token: verify_token_here
Content-Type: application/json
Security Best Practices
API Key Security
-
Environment Variables: Store API keys in environment variables, never in code
-
Key Rotation: Rotate API keys regularly through your merchant dashboard
-
Access Control: Use different API keys for different environments (dev, staging, prod)
-
Monitoring: Monitor API key usage for unusual activity
# Good - using environment variables
export piaxis_API_KEY="pk_live_1234567890abcdef"
OAuth2 Security
-
State Parameter: Always use the state parameter to prevent CSRF attacks
-
HTTPS Only: Only use OAuth2 over HTTPS connections
-
Token Storage: Store tokens securely (encrypted, server-side)
-
Scope Limitation: Request only the scopes your application needs
Request Security
-
HTTPS Required: All API requests must use HTTPS
-
Request Signing: Consider implementing request signing for additional security
-
Idempotency: Use idempotency keys for critical operations
-
Rate Limiting: Implement client-side rate limiting to avoid hitting API limits
Error Handling
Authentication Errors
HTTP Code |
Error Code |
Description |
|---|---|---|
401 |
|
API key is invalid or missing |
401 |
|
OAuth access token has expired |
403 |
|
Token doesn’t have required permissions |
403 |
|
Operation requires OTP verification |
400 |
|
OTP code is invalid or expired |
429 |
|
Too many requests, implement backoff |
Example Error Response:
{
"error": {
"code": "INVALID_API_KEY",
"message": "The provided API key is invalid",
"details": {
"timestamp": "2024-01-15T10:30:00Z",
"request_id": "req_1234567890"
}
}
}
Implementation Examples
Python Implementation
import os
import requests
from datetime import datetime, timedelta
class PiaxisAuth:
def __init__(self, api_key=None, client_id=None):
self.api_key = api_key or os.getenv('piaxis_API_KEY')
self.client_id = client_id or os.getenv('piaxis_CLIENT_ID')
self.access_token = None
self.refresh_token = None
self.token_expires = None
self.base_url = 'https://api.gopiaxis.com'
def get_api_headers(self):
"""Get headers for API key authentication"""
return {
'api-key': self.api_key,
'Content-Type': 'application/json'
}
def get_oauth_headers(self):
"""Get headers for OAuth authentication"""
if not self.access_token or self.is_token_expired():
raise Exception("No valid access token available")
return {
'Authorization': f'Bearer {self.access_token}',
'Content-Type': 'application/json'
}
def get_authorization_url(self, redirect_uri, scopes, state=None):
"""Generate OAuth2 authorization URL"""
params = {
'merchant_id': self.client_id,
'redirect_uri': redirect_uri,
'response_type': 'code',
'scope': ' '.join(scopes)
}
if state:
params['state'] = state
query_string = '&'.join([f"{k}={v}" for k, v in params.items()])
return f"{self.base_url}/api/authorize?{query_string}"
def exchange_code_for_token(self, code, redirect_uri):
"""Exchange authorization code for access token"""
data = {
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': redirect_uri,
'client_id': self.client_id
}
response = requests.post(
f"{self.base_url}/api/oauth/token",
data=data,
headers={'Content-Type': 'application/x-www-form-urlencoded'}
)
if response.status_code == 200:
token_data = response.json()
self.access_token = token_data['access_token']
self.refresh_token = token_data.get('refresh_token')
self.token_expires = datetime.now() + timedelta(seconds=token_data['expires_in'])
return token_data
else:
raise Exception(f"Token exchange failed: {response.text}")
def refresh_access_token(self):
"""Refresh expired access token"""
if not self.refresh_token:
raise Exception("No refresh token available")
data = {
'grant_type': 'refresh_token',
'refresh_token': self.refresh_token,
'client_id': self.client_id
}
response = requests.post(
f"{self.base_url}/api/oauth/refresh",
data=data,
headers={'Content-Type': 'application/x-www-form-urlencoded'}
)
if response.status_code == 200:
token_data = response.json()
self.access_token = token_data['access_token']
self.token_expires = datetime.now() + timedelta(seconds=token_data['expires_in'])
return token_data
else:
raise Exception(f"Token refresh failed: {response.text}")
def is_token_expired(self):
"""Check if access token is expired"""
if not self.token_expires:
return True
return datetime.now() >= self.token_expires
def request_otp(self, operation, **kwargs):
"""Request OTP for sensitive operation"""
data = {'operation': operation, **kwargs}
response = requests.post(
f"{self.base_url}/api/auth/request-otp",
json=data,
headers=self.get_api_headers()
)
if response.status_code == 200:
return response.json()
else:
raise Exception(f"OTP request failed: {response.text}")
def verify_otp(self, otp_id, otp_code):
"""Verify OTP code"""
data = {
'otp_id': otp_id,
'otp_code': otp_code
}
response = requests.post(
f"{self.base_url}/api/auth/verify-otp",
json=data,
headers=self.get_api_headers()
)
if response.status_code == 200:
return response.json()
else:
raise Exception(f"OTP verification failed: {response.text}")
# Usage example
auth = PiaxisAuth()
# API Key authentication
headers = auth.get_api_headers()
# OAuth2 flow
auth_url = auth.get_authorization_url(
redirect_uri='https://yourapp.com/callback',
scopes=['payments:create', 'escrow:create'],
state='random-state-string'
)
print(f"Redirect user to: {auth_url}")
# After callback, exchange code for token
token_data = auth.exchange_code_for_token('auth_code_here', 'https://yourapp.com/callback')
# Use OAuth headers for subsequent requests
oauth_headers = auth.get_oauth_headers()
Node.js Implementation
const axios = require('axios');
class PiaxisAuth {
constructor(options = {}) {
this.apiKey = options.apiKey || process.env.piaxis_API_KEY;
this.clientId = options.clientId || process.env.piaxis_CLIENT_ID;
this.accessToken = null;
this.refreshToken = null;
this.tokenExpires = null;
this.baseUrl = 'https://api.gopiaxis.com';
}
getApiHeaders() {
return {
'api-key': this.apiKey,
'Content-Type': 'application/json'
};
}
getOAuthHeaders() {
if (!this.accessToken || this.isTokenExpired()) {
throw new Error('No valid access token available');
}
return {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json'
};
}
getAuthorizationUrl(redirectUri, scopes, state = null) {
const params = new URLSearchParams({
merchant_id: this.clientId,
redirect_uri: redirectUri,
response_type: 'code',
scope: scopes.join(' ')
});
if (state) {
params.append('state', state);
}
return `${this.baseUrl}/api/authorize?${params.toString()}`;
}
async exchangeCodeForToken(code, redirectUri) {
const data = new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: redirectUri,
client_id: this.clientId
});
try {
const response = await axios.post(`${this.baseUrl}/api/oauth/token`, data, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
const tokenData = response.data;
this.accessToken = tokenData.access_token;
this.refreshToken = tokenData.refresh_token;
this.tokenExpires = new Date(Date.now() + tokenData.expires_in * 1000);
return tokenData;
} catch (error) {
throw new Error(`Token exchange failed: ${error.response?.data || error.message}`);
}
}
async refreshAccessToken() {
if (!this.refreshToken) {
throw new Error('No refresh token available');
}
const data = new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: this.refreshToken,
client_id: this.clientId
});
try {
const response = await axios.post(`${this.baseUrl}/api/oauth/refresh`, data, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
const tokenData = response.data;
this.accessToken = tokenData.access_token;
this.tokenExpires = new Date(Date.now() + tokenData.expires_in * 1000);
return tokenData;
} catch (error) {
throw new Error(`Token refresh failed: ${error.response?.data || error.message}`);
}
}
isTokenExpired() {
if (!this.tokenExpires) return true;
return new Date() >= this.tokenExpires;
}
async requestOtp(operation, additionalData = {}) {
const data = { operation, ...additionalData };
try {
const response = await axios.post(`${this.baseUrl}/api/auth/request-otp`, data, {
headers: this.getApiHeaders()
});
return response.data;
} catch (error) {
throw new Error(`OTP request failed: ${error.response?.data || error.message}`);
}
}
async verifyOtp(otpId, otpCode) {
const data = {
otp_id: otpId,
otp_code: otpCode
};
try {
const response = await axios.post(`${this.baseUrl}/api/auth/verify-otp`, data, {
headers: this.getApiHeaders()
});
return response.data;
} catch (error) {
throw new Error(`OTP verification failed: ${error.response?.data || error.message}`);
}
}
}
// Usage example
const auth = new PiaxisAuth();
// API Key authentication
const headers = auth.getApiHeaders();
// OAuth2 flow
const authUrl = auth.getAuthorizationUrl(
'https://yourapp.com/callback',
['payments:create', 'escrow:create'],
'random-state-string'
);
console.log('Redirect user to:', authUrl);
// After callback, exchange code for token
auth.exchangeCodeForToken('auth_code_here', 'https://yourapp.com/callback')
.then(tokenData => {
console.log('Token obtained:', tokenData);
// Use OAuth headers for subsequent requests
const oauthHeaders = auth.getOAuthHeaders();
})
.catch(error => {
console.error('Authentication failed:', error.message);
});
module.exports = PiaxisAuth;
Rate Limiting & Monitoring
Rate Limits
Piaxis implements rate limiting to ensure fair usage and system stability:
Endpoint Category |
Rate Limit |
Description |
|---|---|---|
Authentication |
100/hour |
OAuth token requests, OTP operations |
Payment Creation |
1000/hour |
Creating new payments |
Escrow Operations |
500/hour |
Creating, releasing, reversing escrows |
Data Retrieval |
5000/hour |
Getting payment/escrow status |
Webhooks |
N/A |
Unlimited (outbound from Piaxis) |
Rate Limit Headers:
All API responses include rate limiting information:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1642694400
Handling Rate Limits:
import time
import requests
def make_api_request_with_backoff(url, headers, data, max_retries=3):
for attempt in range(max_retries):
response = requests.post(url, headers=headers, json=data)
if response.status_code == 429:
# Rate limited, check retry-after header
retry_after = int(response.headers.get('Retry-After', 60))
print(f"Rate limited, waiting {retry_after} seconds...")
time.sleep(retry_after)
continue
return response
raise Exception("Max retries exceeded due to rate limiting")
Monitoring & Logging
Request ID Tracking:
Every API response includes a unique request ID for tracking:
{
"data": {...},
"meta": {
"request_id": "req_1234567890abcdef",
"timestamp": "2024-01-15T10:30:00Z"
}
}
Recommended Logging:
import logging
import json
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def log_api_call(method, url, request_data, response_data, status_code, request_id):
log_entry = {
'method': method,
'url': url,
'request_data': request_data,
'response_data': response_data,
'status_code': status_code,
'request_id': request_id,
'timestamp': datetime.now().isoformat()
}
if status_code >= 400:
logger.error(f"API Error: {json.dumps(log_entry)}")
else:
logger.info(f"API Success: {json.dumps(log_entry)}")
Support & Resources
Documentation: - API Reference: https://docs.gopiaxis.com/api/ - Integration Guides: https://docs.gopiaxis.com/guides/ - SDKs: https://github.com/piaxis/
Support Channels: - Email: api-support@piaxis.com - Developer Forum: https://forum.gopiaxis.com/ - Status Page: https://status.gopiaxis.com/
Emergency Contact: For critical production issues: emergency@piaxis.com
Testing: - Sandbox API: https://sandbox-api.gopiaxis.com - Test Cards: Available in merchant dashboard - Webhook Testing: Use ngrok or similar tools for local development