Getting Started
ArchPay API Reference
ArchPay gives you a simple REST API to trigger M-Pesa STK Push payments, verify payment status, and receive real-time webhook callbacks. One API key. No SDK required.
Base URL
https://pay.archietech.app/api/v1
Content-Type โ All requests must include Content-Type: application/json
Auth header โ All requests must include x-api-key: YOUR_API_KEY
Authentication
API Key Authentication
Every request to the API must include your API key in the x-api-key header. You receive your key immediately on account creation. Keys start with apk_.
x-api-key: apk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
Keep your key secret. Do not expose it in frontend JavaScript or public repositories. Rotate it immediately from your dashboard if compromised.
Alternative: Public Key + Secret Key
For client-side or split-credential use, you can authenticate with your public key and secret key as separate headers instead of the combined API key.
x-public-key: pk_xxxxxxxxxxxxxxxxxxxx
x-secret-key: sk_xxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
Configuration
Base URL & Versioning
The current API version is v1. All endpoints are prefixed with /api/v1.
https://pay.archietech.app/api/v1
GET https://pay.archietech.app/health
// Response
{
"status": "ok",
"service": "ArchPay",
"version": "2.0.0",
"ts": "2025-01-01T12:00:00.000Z"
}
Endpoints
Trigger STK Push
Sends an M-Pesa payment prompt to the customer's phone. The customer sees a PIN entry dialog. Consumes 1 credit per call.
POST
/stkpush
pay.archietech.app/api/v1/stkpush
Request Body
| Parameter | Type | Description |
| phonerequired | string | Safaricom number in 254XXXXXXXXX format (12 digits, starts with 254) |
| amountrequired | integer | Amount in KES. Must be a whole number โฅ 1. Max: 300,000 |
| accountReferenceoptional | string | Reference shown on customer M-Pesa statement. Max 12 chars. Defaults to your business name. |
| descriptionoptional | string | Transaction description. Max 20 chars. |
| channelIdoptional | string | Route payment through a specific channel (Paybill / Till / Bank). Get channel IDs from GET /channels. If omitted, uses platform default. |
๐ก Sandbox mode โ When your account is in sandbox mode, STK pushes are simulated (no real M-Pesa calls). The response is identical but "isSandbox": true is set on the transaction. Use sandbox to test your integration without spending credits or triggering real payments.
curl -X POST https://pay.archietech.app/api/v1/stkpush \
-H "x-api-key: apk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"phone": "254712345678",
"amount": 1500,
"accountReference": "INV-001",
"description": "Order payment"
}'
{
"success": true,
"message": "STK push sent. Awaiting customer PIN.",
"checkoutRequestId": "ws_CO_271020231234567890",
"merchantRequestId": "29115-34620561-1",
"channelId": "CHN123456", // null if default channel
"creditsRemaining": 9
}
{
"success": false,
"code": "INSUFFICIENT_CREDITS",
"error": "Insufficient credits"
}
Endpoints
List Channels
Returns all payment channels configured for your account (Paybill, Till, Banks). Use the channelId from this response in the /stkpush request to route payments through a specific channel.
GET
/channels
pay.archietech.app/api/v1/channels
curl https://pay.archietech.app/api/v1/channels \
-H "x-api-key: apk_your_key_here"
{
"success": true,
"channels": [
{
"channelId": "CHN123456", // use this in /stkpush
"name": "Shop Till",
"type": "till", // paybill | till | bank_kcb | bank_equity | โฆ
"provider": "m-pesa",
"active": true
},
{
"channelId": "CHN789012",
"name": "Main Paybill",
"type": "paybill",
"provider": "m-pesa",
"active": true
}
]
}
To route an STK push through a specific channel, include "channelId": "CHN123456" in your /stkpush body. Channels not owned by your account or inactive channels return CHANNEL_NOT_FOUND / CHANNEL_INACTIVE.
Endpoints
Verify Payment Status
Poll the status of a specific STK push transaction using its checkoutRequestId. Use this as a fallback โ webhooks are the recommended way to receive results.
POST
/verify
pay.archietech.app/api/v1/verify
| Parameter | Type | Description |
| checkoutRequestIdrequired | string | The checkoutRequestId returned by the /stkpush call |
curl -X POST https://pay.archietech.app/api/v1/verify \
-H "x-api-key: apk_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "checkoutRequestId": "ws_CO_271020231234567890" }'
{
"success": true,
"status": "completed",
"mpesaReceiptNumber": "RFP20XXXXXXX",
"amount": 1500,
"phone": "254712345678",
"transactionDate": "20250101120000"
}
Endpoints
List Transactions
Returns a paginated list of all transactions made with your API key, sorted by most recent first.
GET
/transactions
pay.archietech.app/api/v1/transactions
| Query Parameter | Type | Description |
| limitoptional | integer | Max results to return. Default: 50, Max: 200 |
| statusoptional | string | Filter by status: completed, pending, failed, cancelled, timeout |
curl https://pay.archietech.app/api/v1/transactions?limit=50 \
-H "x-api-key: apk_your_key_here"
{
"success": true,
"transactions": [
{
"_id": "64f...",
"checkoutRequestId": "ws_CO_271020231234567890",
"phone": "254712345678",
"amount": 1500,
"status": "completed",
"mpesaReceiptNumber": "RFP20XXXXXXX",
"accountReference": "INV-001",
"createdAt": "2025-01-01T12:00:00.000Z"
}
],
"count": 1
}
Endpoints
Check Credit Balance
Returns the current credit balance for your account. Each successful STK push consumes 1 credit.
GET
/balance
pay.archietech.app/api/v1/balance
curl https://pay.archietech.app/api/v1/balance \
-H "x-api-key: apk_your_key_here"
{
"success": true,
"credits": 47,
"businessName": "Acme Ltd"
}
Webhooks
Webhook Payload
When a payment is confirmed or fails, ArchPay sends an HTTP POST to your configured webhook URL. This happens within seconds of M-Pesa processing the transaction.
{
"event": "payment.callback",
"status": "completed",
"checkoutRequestId": "ws_CO_271020231234567890",
"mpesaReceiptNumber": "RFP20XXXXXXX", // null if not completed
"amount": 1500,
"phone": "254712345678",
"accountReference": "INV-001",
"timestamp": "2025-01-01T12:00:00.000Z"
}
Your webhook endpoint must return a 2xx HTTP status within 10 seconds. ArchPay does not currently retry failed webhook deliveries โ ensure your endpoint is reliable.
Webhooks
Payment Statuses
Every transaction has one of these statuses. Only completed means money moved.
completed
Payment confirmed. M-Pesa receipt number present. Money received.
pending
STK push sent, awaiting customer PIN entry. Poll or wait for webhook.
failed
Payment failed. Could be wrong PIN, insufficient funds, or M-Pesa error.
cancelled
Customer dismissed or cancelled the STK prompt.
timeout
Customer did not respond to the STK prompt within the allowed window.
Webhooks
Configuring Your Webhook
Set your webhook URL from the Settings tab in your dashboard. It must be a publicly accessible HTTPS endpoint.
app.post('/webhooks/mpesa', (req, res) => {
const { event, status, checkoutRequestId, mpesaReceiptNumber, amount, phone } = req.body;
if (status === 'completed') {
// Payment confirmed โ fulfill the order
console.log(`Payment ${mpesaReceiptNumber}: KSH ${amount} from ${phone}`);
} else if (status === 'failed' || status === 'cancelled') {
// Payment did not go through
console.log(`Payment ${checkoutRequestId} ${status}`);
}
res.status(200).json({ received: true }); // must return 2xx
});
Getting Started
Error Handling
All errors return JSON with "success": false, an "error" message, and a machine-readable "code" string for programmatic handling.
{
"success": false,
"code": "INSUFFICIENT_CREDITS", // machine-readable code
"error": "Insufficient credits" // human-readable message
}
Error Codes
| Code | HTTP | Meaning |
| MISSING_FIELDS | 400 | Required fields missing from request body |
| INVALID_PHONE | 400 | Phone number not in 254XXXXXXXXX format |
| INVALID_AMOUNT | 400 | Amount is not a positive integer |
| UNAUTHORIZED | 401 | Missing or invalid API key |
| ACCOUNT_DISABLED | 403 | Your account has been suspended |
| INSUFFICIENT_CREDITS | 402 | Account credits exhausted โ top up to continue |
| CHANNEL_NOT_FOUND | 404 | channelId does not exist or does not belong to your account |
| CHANNEL_INACTIVE | 403 | Channel exists but is currently disabled |
| B2C_DISABLED | 403 | B2C payments are not enabled on this account |
| SERVICE_UNAVAILABLE | 503 | Platform maintenance mode is active โ retry shortly |
| MPESA_ERROR | 502 | Safaricom API returned an error โ check details field |
HTTP Status Codes
| Status | Meaning | Common cause |
| 400 | Bad Request | Missing required fields, invalid phone format, or non-integer amount |
| 401 | Unauthorized | Missing or invalid x-api-key header |
| 402 | Payment Required | Account has zero credits โ top up to continue |
| 403 | Forbidden | Account suspended, channel inactive, or B2C disabled |
| 404 | Not Found | Transaction or channel ID not found |
| 502 | Bad Gateway | M-Pesa API error โ check the details field for Safaricom's error message |
| 503 | Service Unavailable | Platform under maintenance โ retry after a moment |
Guides
Quick Start
From zero to your first STK push in 3 steps.
# Sign up at https://pay.archietech.app โ Get Started
# Your API key appears immediately on the dashboard (starts with apk_)
curl -X POST https://pay.archietech.app/api/v1/stkpush \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "phone": "2547XXXXXXXX", "amount": 1 }'
# In your dashboard โ Settings โ Webhook URL
# Enter: https://yourapp.com/webhooks/mpesa
# Then handle the POST in your server (see Webhook section above)
Guides
Node.js Integration
const ARCHPAY_KEY = process.env.ARCHPAY_API_KEY;
const BASE = 'https://pay.archietech.app/api/v1';
async function stkPush(phone, amount, reference) {
const res = await fetch(`${BASE}/stkpush`, {
method: 'POST',
headers: {
'x-api-key': ARCHPAY_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ phone, amount, accountReference: reference }),
});
return res.json();
}
// Usage
const result = await stkPush('254712345678', 500, 'ORDER-001');
console.log(result.checkoutRequestId); // save this to verify later
Guides
Python Integration
import requests, os
ARCHPAY_KEY = os.environ['ARCHPAY_API_KEY']
BASE = 'https://pay.archietech.app/api/v1'
HEADERS = {'x-api-key': ARCHPAY_KEY, 'Content-Type': 'application/json'}
def stk_push(phone, amount, reference='Payment'):
r = requests.post(
f'{BASE}/stkpush',
headers=HEADERS,
json={'phone': phone, 'amount': amount, 'accountReference': reference}
)
return r.json()
# Usage
result = stk_push('254712345678', 500, 'ORDER-001')
print(result['checkoutRequestId']) # save for verification