Escrow API
This page documents the live public escrow surface served under
https://api.gopiaxis.com/api.
Overview
Public escrow routes:
POST /api/escrows/GET /api/escrows/{escrow_id}GET /api/escrows/{escrow_id}/statusPOST /api/escrows/{escrow_id}/terms/{term_id}/fulfillPOST /api/escrows/{escrow_id}/releasePOST /api/escrows/{escrow_id}/reversePOST /api/escrows/{escrow_id}/disputes
Public Contract Notes
The guaranteed create-escrow request schema is:
receiver_id(optional; defaults to the authenticated merchant account)amountcurrency_codepayment_methodtermsexternal_user_iduser_infouser_locationexternal_order_idallocationsmetadata
This differs intentionally from direct payments:
direct payment uses
currencyandrecipient_idescrow uses
currency_codeandreceiver_iddirect payment credits the authenticated merchant collector by default
public escrow collection also defaults the release destination to the authenticated merchant
if
receiver_idis supplied, it must match the authenticated merchantAccount.idreceiver_idmust be a PiaxisAccount.id; do not send aMerchantProfile.idorUserProfile.id
Some long-form examples may include richer descriptive keys inside term payloads. That is acceptable as term-specific data, but the guaranteed outer request contract is the field list above.
Authentication
Merchant-controlled calls use:
api-key: YOUR_MERCHANT_API_KEY
Content-Type: application/json
X-piaxis-Client-ID: YOUR_MERCHANT_CLIENT_ID
For piaxis_external sender flows, use OAuth bearer auth where the route
requires user-context authorization.
For unregistered external-money flows, request an OTP first with
POST /api/request-otp and carry it in user_info.otp.
Marketplace Allocations
For marketplace orders where the buyer pays once but the merchant remains the only
escrow receiver, create one escrow to the merchant and represent internal seller,
fulfillment, or service slices with allocations.
This is the recommended pattern when:
the merchant is the contractual counterparty and must remain accountable for settlement
downstream sellers are not individually onboarded as direct escrow receivers
the payer should only be charged once for the order
Do not create one payer-funded escrow per downstream seller for the same order when the merchant remains the sole escrow receiver. Use one merchant-held escrow instead.
Allocation-aware create requests additionally support:
external_order_idfor merchant-side order reconciliationallocationsfor per-slice amounts and references; totals must sum to the escrowamountmetadatafor merchant-defined JSON that should remain attached to the escrow
Allocation-aware responses additionally include:
balance_escrow_inandbalance_escrow_outfor remaining releasable and reversible balancespersisted
metadatanormalized
allocationsallocation_summarywith pending, released, and reversed counts and amounts
Merchants should keep these allocation balances and partial action controls visible in the same order-management surface where the user placed the order.
Create Escrow
- POST /api/escrows/
Create a new escrow.
Canonical MTN escrow example
POST /api/escrows/ HTTP/1.1
Host: api.gopiaxis.com
api-key: YOUR_API_KEY
Content-Type: application/json
X-piaxis-Client-ID: YOUR_MERCHANT_CLIENT_ID
{
"amount": "50000.00",
"currency_code": "UGX",
"payment_method": "mtn",
"terms": [
{
"type": "delivery_confirmation",
Allocation-aware marketplace create example
{
"amount": "50000.00",
"currency_code": "UGX",
"payment_method": "mtn",
"external_order_id": "ORDER-789",
"allocations": [
{
"allocation_key": "seller-alpha",
"amount": "20000.00",
"seller_reference": "seller-001",
"description": "Alpha seller settlement"
},
{
"allocation_key": "seller-beta",
"amount": "30000.00",
"seller_reference": "seller-002",
"description": "Beta seller settlement"
}
],
"metadata": {
"channel": "marketplace"
},
"terms": [],
"user_info": {
"email": "[email protected]",
"phone_number": "+256700000000",
"otp": "123456"
}
}
"data": {
"deadline": "2026-12-31T23:59:59Z"
}
}
],
"user_info": {
"email": "[email protected]",
"phone_number": "+256700000000",
"otp": "123456"
}
}
Receiver semantics
Public escrow collection infers the release destination from the authenticated merchant API key.
omitting
receiver_iduses the authenticated merchantAccount.idautomaticallyif
receiver_idis supplied, it must still match the authenticated merchantAccount.idthis public collection route does not use
receiver_idto choose some other merchant or platform accountif you want to hold funds through escrow and later pay an internal or external beneficiary, release into the authenticated merchant wallet first and then create a disbursement
Marketplace sequence
For a platform-managed order or service flow:
authenticate as the merchant or platform account that should collect and create the escrow
the customer pays into escrow
release the escrow to that same authenticated merchant or platform account when the order or service is complete
if the final beneficiary should be paid on MTN or Airtel mobile money, create a separate direct disbursement to the beneficiary phone number
This keeps the roles explicit:
the authenticated merchant receives the escrow release on this public collection route
recipients[].recipient_idis only for later internal wallet payoutsexternal mobile-money payees are handled later through the disbursement routes with
phone_number
Choose the right flow
Use direct payment when the authenticated merchant should collect immediately and no later escrow fulfillment is needed.
Use public escrow collection when the authenticated merchant should still be the final collector, but release should wait for payer and merchant actions such as delivery confirmation or meeting verification.
Use direct disbursement when you already control the collected balance and want to pay a beneficiary immediately.
Use escrow disbursement when the payout beneficiary, not the collecting merchant, must satisfy terms before payout. In escrow-disbursement batches the merchant or platform is the escrow sender and the beneficiary becomes the escrow receiver.
If your rule is collect from buyer B now, pay user C later, keep the collection on POST /api/escrows/ with the merchant as receiver, release into the merchant wallet, and then create a direct disbursement or escrow disbursement for user C.
Canonical piaxis_external escrow example
POST /api/escrows/ HTTP/1.1
Host: api.gopiaxis.com
api-key: YOUR_API_KEY
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
{
"amount": "75000.00",
"currency_code": "UGX",
"payment_method": "piaxis_external",
"external_user_id": "platform_user_123",
"terms": [
{
"type": "delivery_confirmation",
"data": {
"deadline": "2026-12-31T23:59:59Z"
}
}
]
}
Guaranteed create response shape
{
"id": "7680cfa9-b0cf-43a7-974b-108b89bd5ebe",
"amount": "50000.00",
"currency_code": "UGX",
"status": "pending",
"payment_method": "mtn",
"sender_id": null,
"receiver_id": "096b723a-45c5-4957-94d7-747835136265",
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-01-15T10:30:00Z",
"terms": [],
"products": [],
"location": null,
"required_actions": [],
"external_user_id": null
}
Allocation-aware marketplace create example
{
"amount": "50000.00",
"currency_code": "UGX",
"payment_method": "mtn",
"external_order_id": "ORDER-789",
"allocations": [
{
"allocation_key": "seller-alpha",
"amount": "20000.00",
"seller_reference": "seller-001",
"description": "Alpha seller settlement"
},
{
"allocation_key": "seller-beta",
"amount": "30000.00",
"seller_reference": "seller-002",
"description": "Beta seller settlement"
}
],
"metadata": {
"channel": "marketplace"
},
"terms": [],
"user_info": {
"email": "[email protected]",
"phone_number": "+256700000000",
"otp": "123456"
}
}
When allocations are present, create and read responses may also include:
balance_escrow_inbalance_escrow_outmetadataallocationsallocation_summary
Read Escrow
- GET /api/escrows/{escrow_id}
Fetch the escrow record in the canonical typed response shape.
- GET /api/escrows/{escrow_id}/status
Fetch an operational status view with buyer info, term statuses, and
all_terms_met.
The two routes are both public and intentionally return different detail shapes.
Fulfill Terms
- POST /api/escrows/{escrow_id}/terms/{term_id}/fulfill
Fulfill a single escrow term.
Role model on this public collection route
For escrows created by POST /api/escrows/ the protected actors are:
payer or customer = escrow sender
authenticated merchant collector = escrow receiver
That means:
buyer-side confirmations must come from the original payer
seller-side confirmations must come from the authenticated merchant whose account matches
receiver_idif you want a downstream seller, driver, or beneficiary to become the protected receiver instead, use
POST /api/escrow-disbursementsrather than trying to route collection escrow directly to that user
Inspect required actions first
Use GET /api/escrows/{escrow_id} and inspect required_actions before building a fulfill UI.
Typical action hints returned by the live code include:
delivery_confirmation_requiredwithconfirmation_methoddelivery_meeting_requiredwithmax_distance_kmagreement_requiredwithagreement_optionsand current statusproof_requiredwithproof_typeand optionaldeadline
Term-specific payloads
The outer request shape stays the same, but data keys vary by term_type. The live handler branches on those keys, so send the term-specific payload instead of a generic confirmed flag.
Delivery confirmation
Send confirmation_method plus either buyer_device_info or seller_device_info.
Buyer confirmation example:
{
"term_id": "4215567e-6928-4a9d-85d8-c3b1708bdcec",
"term_type": "delivery_confirmation",
"data": {
"confirmation_method": "signature",
"buyer_device_info": {
"device_id": "buyer-device-001",
"platform": "android",
"app_version": "1.0.0"
}
},
"user_info": {
"email": "[email protected]",
"phone_number": "+256700000000",
"otp": "123456"
}
}
Seller confirmation example:
{
"term_id": "4215567e-6928-4a9d-85d8-c3b1708bdcec",
"term_type": "delivery_confirmation",
"data": {
"confirmation_method": "signature",
"seller_device_info": {
"device_id": "merchant-ops-01",
"platform": "web"
}
}
}
Use user_info for an unregistered external payer. Use bearer auth for registered piaxis or piaxis_external payers. Use the authenticated merchant API key for the seller-side confirmation.
Meeting delivery
Buyer confirmation sends buyer_latitude and buyer_longitude. Seller confirmation sends seller_latitude and seller_longitude. Both sides may include device_info. The route records caller IP automatically and only completes the term after both sides submit coordinates within the allowed proximity.
Buyer meeting example:
{
"term_id": "4215567e-6928-4a9d-85d8-c3b1708bdcec",
"term_type": "meeting_delivery",
"data": {
"buyer_latitude": 0.3476,
"buyer_longitude": 32.5825,
"device_info": {
"device_id": "buyer-phone-01",
"platform": "android"
}
},
"user_info": {
"email": "[email protected]",
"phone_number": "+256700000000",
"otp": "123456"
}
}
Seller meeting example:
{
"term_id": "4215567e-6928-4a9d-85d8-c3b1708bdcec",
"term_type": "meeting_delivery",
"data": {
"seller_latitude": 0.3477,
"seller_longitude": 32.5826,
"device_info": {
"platform": "web"
}
}
}
Other supported term types
agreement: sendagreement_typeasrelease,reverse, orsplit; when usingsplit, also sendsplit_percentage. This flow currently requires a registered account actor.location: sendlatitudeandlongitude.meeting: sendlatitudeandlongitude.password: sendpassword.proof: submitfilesfirst; later sendverifyastrueafter the proof is ready to approve.custom: send the term-specificdataexpected by your custom rule.return_period: sendreturn_requestedwhen the buyer is requesting a return within the allowed window.
Guaranteed request fields:
term_idterm_typedatauser_info
Delivery confirmation and meeting-delivery responses include requires_other_party until both sides have submitted their part. Other term types may return pending or release the funds immediately once all terms are met.
Release Escrow
- POST /api/escrows/{escrow_id}/release
Guaranteed request fields:
verification_codeverification_methoduser_infoforcereasonamountallocation_keys
Example
{
"force": true,
"reason": "Buyer requested immediate release",
"user_info": {
"email": "[email protected]",
"phone_number": "+256700000000",
"otp": "123456"
}
}
Allocation-aware partial release example
{
"reason": "Merchant confirmed seller alpha items",
"allocation_keys": ["seller-alpha"],
"amount": "20000.00",
"user_info": {
"email": "[email protected]",
"phone_number": "+256700000000",
"otp": "123456"
}
}
Allocation-aware partial release response
{
"status": "released",
"escrow_id": "43452aa2-28b4-4e8f-a1d2-d47014c9a33a",
"amount": "20000.00",
"force": false,
"reason": "Merchant confirmed seller alpha items",
"allocation_keys": ["seller-alpha"],
"allocation_summary": {
"total_count": 2,
"pending_count": 1,
"released_count": 1,
"reversed_count": 0,
"pending_amount": "30000.00",
"released_amount": "20000.00",
"reversed_amount": "0.00"
}
}
Short-lived approval session for nearby partial releases
For external payer escrows created with mtn, airtel, or card where the payer is
still an unregistered external user, the first successful release request that includes
user_info.email, user_info.phone_number, and user_info.otp opens a short-lived
approval session scoped to that escrow.
During that approval window:
subsequent nearby partial releases on the same escrow may omit
user_info.otpuser_info.emailanduser_info.phone_numbermust still be sent and must still match the original payerif the approval window expires, send a fresh OTP again before the next release
Example follow-up partial release without a fresh OTP
{
"reason": "Merchant confirmed seller beta items",
"allocation_keys": ["seller-beta"],
"amount": "30000.00",
"user_info": {
"email": "[email protected]",
"phone_number": "+256700000000"
}
}
Reverse Escrow
amount and allocation_keys are also supported here for allocation-aware partial
reversals on marketplace escrows.
- POST /api/escrows/{escrow_id}/reverse
Guaranteed request fields:
reasonverification_codeverification_methoduser_infoamountallocation_keys
Allocation-aware partial reverse example
{
"reason": "Seller beta items were cancelled",
"allocation_keys": ["seller-beta"],
"amount": "30000.00",
"user_info": {
"email": "[email protected]",
"phone_number": "+256700000000",
"otp": "123456"
}
}
Allocation-aware partial reverse response
{
"status": "reversed",
"escrow_id": "43452aa2-28b4-4e8f-a1d2-d47014c9a33a",
"amount": "30000.00",
"reason": "Seller beta items were cancelled",
"allocation_keys": ["seller-beta"],
"allocation_summary": {
"total_count": 2,
"pending_count": 0,
"released_count": 1,
"reversed_count": 1,
"pending_amount": "0.00",
"released_amount": "20000.00",
"reversed_amount": "30000.00"
}
}
Allocation-Aware Partial Reverse:
POST /api/escrows/43452aa2-28b4-4e8f-a1d2-d47014c9a33a/reverse HTTP/1.1
Host: api.gopiaxis.com
api-key: YOUR_API_KEY
Content-Type: application/json
{
"reason": "Seller beta items were cancelled",
"allocation_keys": ["seller-beta"],
"amount": "30000.00",
"user_info": {
"email": "[email protected]",
"phone_number": "+256700000000",
"otp": "123456"
}
}
Allocation-Aware Partial Reverse Response:
{
"status": "reversed",
"escrow_id": "43452aa2-28b4-4e8f-a1d2-d47014c9a33a",
"amount": "30000.00",
"reason": "Seller beta items were cancelled",
"allocation_keys": ["seller-beta"],
"allocation_summary": {
"total_count": 2,
"pending_count": 0,
"released_count": 1,
"reversed_count": 1,
"pending_amount": "0.00",
"released_amount": "20000.00",
"reversed_amount": "30000.00"
}
}
Disputes
- POST /api/escrows/{escrow_id}/disputes
Guaranteed request fields:
reasoninitiator_roleuser_info
Implementation Notes
Persist the returned
escrow_idimmediately.Reconcile state from both polling and webhooks.
Model term fulfillment as idempotent on your side.
Authenticate collections as the merchant or platform account that should collect released funds.
Keep marketplace allocation balances and partial release or reverse actions visible on the order page where the user placed the order.
Persist
external_order_idand stableallocation_keyvalues for reconciliation across your OMS, support tooling, and settlement reports.Use disbursement recipients, not
receiver_id, when you need to route money to a different internal or external beneficiary later.