Authentication & Security
This page documents the live public authentication surface served under
https://api.gopiaxis.com/api.
Overview
The public payment API uses three authentication patterns:
Merchant API key for server-to-server payment, escrow, and disbursement calls
OAuth bearer token when acting on behalf of a registered Piaxis user
OTP for unregistered or sensitive external-money flows
Public Contract Notes
The current public router exposes these authentication endpoints:
GET /api/authorizePOST /api/tokenPOST /api/request-otp
The current public router does not expose these older paths:
/api/oauth/token/api/oauth/refresh/api/auth/request-otp/api/auth/verify-otp
API Key Authentication
Merchant-controlled server-to-server calls use your merchant API key.
Environment variable naming used throughout these docs and SDK examples:
PIAXIS_API_KEY: your merchant API keyPIAXIS_CLIENT_ID: your merchant client identifierPIAXIS_CLIENT_SECRET: your OAuth client secret
The PIAXIS_CLIENT_ID value is not only used for POST /api/token. It is also sent
as the X-piaxis-Client-ID header on merchant-secured escrow and disbursement routes.
Current binding model:
one
api-keyresolves to one merchant profilethat merchant profile resolves to one merchant account
that merchant account id is the merchant’s own Piaxis account id
merchant_id,receiver_id, andrecipient_idstill refer to PiaxisAccount.idvalues whenever those fields appearon public collection routes, Piaxis derives the merchant collector from the authenticated
api-keydo not send a
MerchantProfile.idorUserProfile.idin those fields
In other words, the public payment API does not treat one API key as a multi-merchant key. If you are building a platform, the usual pattern is a single platform merchant account that collects first and then disburses onward, or explicit connected-account mapping for beneficiaries.
Baseline required headers
api-key: YOUR_MERCHANT_API_KEY
Content-Type: application/json
Additional required header on merchant-secured payout and escrow routes
These routes reject requests without the merchant client identifier header and return
MISSING_CLIENT_ID when it is absent.
This currently includes:
POST /api/escrows/GET /api/merchant-paymentsPOST /api/disbursements/quotePOST /api/disbursementsGET /api/disbursementsGET /api/disbursements/{disbursement_id}POST /api/disbursements/{disbursement_id}/cancelPOST /api/escrow-disbursementsGET /api/escrow-disbursementsGET /api/escrow-disbursements/{disbursement_id}POST /api/escrow-disbursements/{disbursement_id}/releasePOST /api/escrow-disbursements/{disbursement_id}/cancel
X-piaxis-Client-ID: YOUR_MERCHANT_CLIENT_ID
Example
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",
"user_info": {
"phone_number": "+256700000000"
}
}
Use the same merchant client identifier as a header on the merchant-secured route families listed above:
api-key: YOUR_MERCHANT_API_KEY
X-piaxis-Client-ID: YOUR_MERCHANT_CLIENT_ID
Content-Type: application/json
Merchant account id and collection defaults
On the public collection routes, Piaxis derives the merchant account directly from the
authenticated api-key.
That means:
direct payments credit the authenticated merchant account automatically
public escrows also default
receiver_idto that same authenticated merchant accountif you send
recipient_idon direct payments orreceiver_idon public escrows, it must match the authenticated merchantAccount.idthe public payment API does not currently expose a dedicated merchant
whoamior/api/meroute for API-key callers because standard collections do not need one
OAuth2 Flow
Use OAuth when the request acts on behalf of a registered Piaxis user, especially
for piaxis_external payment flows.
Token Exchange
- POST /api/token
Exchange an authorization code for tokens.
- Form Parameters:
grant_type – Must be
authorization_codecode – Authorization code returned by
/api/authorizeredirect_uri – Same redirect URI used during authorization
client_id – Merchant OAuth client id
client_secret – Merchant OAuth client secret
Example
POST /api/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=merchant_client_id&client_secret=merchant_client_secret
Response
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user_id": "096b723a-45c5-4957-94d7-747835136265",
"merchant_id": "f530533e-3761-4cde-9c9d-88c5be6493bb",
"external_user_id": "user_123"
}
Important:
The public token response returns a
refresh_token.The public token response also returns
user_idfor the connected Piaxis account.Persist that
user_idin your own beneficiary directory if you want to send later internal wallet payouts usingrecipients[].recipient_id.The current public
paymentAPIrouter does not expose a dedicated refresh endpoint.For now, plan to restart the authorization flow when you need a fresh public access token.
Using The Bearer Token
Include the token on routes that require user-context authorization:
Authorization: Bearer YOUR_ACCESS_TOKEN
OTP Flow
The public OTP route is used before external-money calls where the payer is not already authenticated as a registered Piaxis user.
Request OTP
- POST /api/request-otp
Request an OTP for an email address, phone number, or both.
- JSON Parameters:
email (string) – Email address (optional)
phone_number (string) – E.164 phone number such as
+256700000000(optional)
Example
POST /api/request-otp HTTP/1.1
Host: api.gopiaxis.com
api-key: YOUR_API_KEY
Content-Type: application/json
{
"email": "[email protected]",
"phone_number": "+256700000000"
}
Use OTP In Money-Moving Calls
There is no standalone public verify-otp route in this router.
Instead:
escrow-family routes for unregistered users carry the OTP in
user_info.otpPOST /api/payments/createalso accepts an optional route parameter namedmfa_code
Escrow example
{
"amount": "50000.00",
"currency_code": "UGX",
"payment_method": "mtn",
"terms": [
{
"type": "delivery_confirmation",
"data": {
"deadline": "2026-12-31T23:59:59Z"
}
}
],
"user_info": {
"email": "[email protected]",
"phone_number": "+256700000000",
"otp": "123456"
}
}
Implementation Examples
Python
import os
import requests
class PiaxisAuth:
def __init__(self, api_key: str, client_id: str, client_secret: str) -> None:
self.api_key = api_key
self.client_id = client_id
self.client_secret = client_secret
self.base_url = "https://api.gopiaxis.com"
def api_headers(self) -> dict[str, str]:
return {
"api-key": self.api_key,
"Content-Type": "application/json",
}
def merchant_secure_headers(self) -> dict[str, str]:
return {
**self.api_headers(),
"X-piaxis-Client-ID": self.client_id,
}
def exchange_code(self, code: str, redirect_uri: str) -> dict:
response = requests.post(
f"{self.base_url}/api/token",
data={
"grant_type": "authorization_code",
"code": code,
"redirect_uri": redirect_uri,
"client_id": self.client_id,
"client_secret": self.client_secret,
},
headers={"Content-Type": "application/x-www-form-urlencoded"},
timeout=30,
)
response.raise_for_status()
return response.json()
def request_otp(self, phone_number: str, email: str | None = None) -> dict:
response = requests.post(
f"{self.base_url}/api/request-otp",
json={"phone_number": phone_number, "email": email},
headers=self.api_headers(),
timeout=30,
)
response.raise_for_status()
return response.json()
piaxis = PiaxisAuth(
api_key=os.environ["PIAXIS_API_KEY"],
client_id=os.environ["PIAXIS_CLIENT_ID"],
client_secret=os.environ["PIAXIS_CLIENT_SECRET"],
)
# Use piaxis.merchant_secure_headers() for merchant-secured escrow and
# disbursement routes that require X-piaxis-Client-ID.
Node.js
const axios = require("axios");
class PiaxisAuth {
constructor({ apiKey, clientId, clientSecret }) {
this.apiKey = apiKey;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.baseUrl = "https://api.gopiaxis.com";
}
apiHeaders() {
return {
"api-key": this.apiKey,
"Content-Type": "application/json",
};
}
merchantSecureHeaders() {
return {
...this.apiHeaders(),
"X-piaxis-Client-ID": this.clientId,
};
}
async exchangeCode(code, redirectUri) {
const response = await axios.post(
`${this.baseUrl}/api/token`,
new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: redirectUri,
client_id: this.clientId,
client_secret: this.clientSecret,
}),
{
headers: { "Content-Type": "application/x-www-form-urlencoded" },
}
);
return response.data;
}
async requestOtp(phoneNumber, email = null) {
const response = await axios.post(
`${this.baseUrl}/api/request-otp`,
{ phone_number: phoneNumber, email },
{ headers: this.apiHeaders() }
);
return response.data;
}
}
// Use piaxis.merchantSecureHeaders() for merchant-secured escrow and
// disbursement routes that require X-piaxis-Client-ID.
Security Best Practices
Keep API keys and OAuth client secrets on the server only.
Use different credentials for sandbox, staging, and production.
Treat OTPs as single-use secrets and expire them quickly in your own UX.
Persist the returned
payment_idorescrow_idand reconcile by id.Verify webhook authenticity before changing internal state.