Create a service order
POST /v1/services/orders : place the purchase and start collection.
POST /v1/services/orders
Creates the service order, opens the collection on payment_method, and
returns the order plus the payment attempt with its next action
(checkout_url or instructions). Fulfilment starts when the payment
settles.
This is a money-moving call: an idempotency key is required, and a stored failure replays instead of re-charging on retry. See Implement Idempotency.
Headers
| Header | Required | Value |
|---|---|---|
Authorization | yes | Bearer sk_... |
Idempotency-Key | yes | Unique per logical order (or idempotency_key body field) |
Content-Type | yes | application/json |
X-Transaction-Authorization | no | Step-up proof when the account requires a PIN |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
country_code | string | yes | Service country |
service_code | string | yes | Catalogue code |
provider_cost_minor | int | yes | Destination service value, minor units |
currency | string | yes | Currency of provider_cost_minor |
payment_method | string | yes | A method from /services/{code}/payment-methods |
idempotency_key | string | yes | Body alternative to the Idempotency-Key header |
quote_id | string | no | Locks pricing; required after cross-currency quotes |
product_code | string | no | Selected plan code for fixed-price services |
operator_code | string | no | Selected operator |
meter_type | string | no | Selected variant (e.g. prepaid) |
account_ref | string | no | Meter/smartcard/customer ID |
phone | string | no | Recipient phone for airtime/data |
validation_snapshot | object | no | The /validate response for the recipient |
metadata | object | no | Your references for reconciliation |
quote_id binds the order to the quoted service, country, amount, and
recipient. A mismatched or expired quote rejects with QUOTE_MISMATCH /
QUOTE_EXPIRED: re-quote instead of retrying.
Request
curl https://api.ouipay.africa/v1/services/orders \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: svc-order-8842" \
-H "Content-Type: application/json" \
-d '{
"country_code": "NG",
"service_code": "electricity",
"provider_cost_minor": 500000,
"currency": "NGN",
"payment_method": "wallet",
"operator_code": "ikeja-electric",
"meter_type": "prepaid",
"account_ref": "04591234567",
"quote_id": "01M2S17..."
}'Response 201
{
"data": {
"order": {
"id": "01M2S18...",
"transaction_id": "01M2S18...",
"payment_transaction_id": null,
"country_code": "NG",
"service_code": "electricity",
"status": "pending",
"customer_amount_minor": 510000,
"fee_minor": 10000,
"provider_cost_minor": 500000,
"currency": "NGN",
"operator_code": "ikeja-electric",
"meter_type": "prepaid",
"account_ref": "04591234567",
"idempotency_key": "svc-order-8842",
"quote_id": "01M2S17...",
"created_at": "2026-09-18T02:00:00Z",
"updated_at": "2026-09-18T02:00:00Z"
},
"payment": {
"id": "01M2S18...",
"transaction_id": "01M2S18...",
"method": "wallet",
"status": "processing"
},
"checkout_url": null,
"instructions": null
}
}Complete the collection the way payment directs: redirect to checkout_url
for hosted methods, follow instructions for transfer-style methods, or wait
for confirmation when wallet debits inline. The order moves to
awaiting_payment, then fulfilling once the payment settles. Track it on
GET /services/orders/{id}/status
or via service_order.completed / service_order.failed
webhooks.
Errors
| HTTP | error.code | Cause |
|---|---|---|
| 400 | VALIDATION | Missing or malformed field |
| 403 | SERVICE_COUNTRY_NOT_ELIGIBLE | Service country not eligible for this account |
| 403 | PIN_REQUIRED | Re-send with X-Transaction-Authorization |
| 403 | COMPLIANCE_DENIED | Screening blocked the order: do not retry |
| 409 | QUOTE_MISMATCH | Quote does not match this service, country, or amount |
| 409 | QUOTE_EXPIRED | Quote expired: re-quote |
| 409 | IDEMPOTENCY_REPLAY | Key reused with a different payload |
| 422 | IDEMPOTENCY_KEY_REQUIRED | Money-moving call without an idempotency key |
| 422 | SERVICE_UNAVAILABLE | Service not offered in the requested country |
| 422 | SERVICE_NOT_EXECUTABLE | Service visible but not purchasable |
| 422 | INSUFFICIENT_FUNDS | Wallet cannot cover customer_amount_minor |
| 422 | LIMIT_EXCEEDED | Above the customer's tier limit |
| 422 | FX_QUOTE_UNAVAILABLE | Cross-currency order without a live FX quote |
| 502 | PROVIDER_FAILURE | Fulfilment rail error: retry-safe via idempotency |