API Reference

HamerzPay Core API

API contract for all HamerzPay surfaces: the five session-authenticated panel APIs (customer, merchant, organization, admin, corporate-ops-under-admin), the corporate B2B partner API, the bank-partner provisioning API, the merchant POS/checkout API, and the hosted online-checkout page. Paths below are written as full, host-relative paths exactly as routed (see routes/api_*.php) rather than relative to a single server, since several panels expose endpoints under the same relative sub-path (e.g. both customer and organization have their own /credit/summary).

Getting started

Every path below is written host-relative, exactly as routed. Several panels expose the same sub-path (customer and organization both have /credit/summary), so always pair a path with its host.

Hosts
Base URLServes
https://hmzptest.irMachine-to-machine APIs — POS (/api/v1/pos), bank partner (/api/v1/bank), corporate B2B (/api/v1/corporate) — plus the hosted checkout page and the inbound provider webhooks. These routes carry no panel host restriction, so they answer on any host served by the app; use the apex.
https://customer.hmzptest.irCustomer panel API (/api/customer) — session-authenticated
https://merchant.hmzptest.irMerchant panel API (/api/merchant) — session-authenticated
https://organization.hmzptest.irOrganization panel API (/api/org) — session-authenticated
https://admin.hmzptest.irAdmin panel API (/api/admin) — session-authenticated
Conventions
MoneyAll amounts are integers in Rial. No decimals, no strings.
IdempotencyEvery mutating machine-to-machine call takes an idempotency key. Replaying a key returns the original result instead of performing the action twice — retry freely on a timeout.
ErrorsFailures return {"success": false, "error": {"code": "...", "message": "..."}}. Branch on error.code, never on the message text.
TimeSignature timestamps are Unix seconds. Your server clock must be within 300 seconds of ours.

Authentication

Three different schemes, depending on which API you are calling. Get this right first — it is where most integrations stall.

API key + secret /api/v1/pos/*

Send both credentials as headers on every request.

X-API-KEY:    hp_live_xxxxxxxxxxxxxxxxxxxxxxxx
X-API-SECRET: <48-character secret>

You receive the pair once, when the merchant is provisioned (or when the merchant rotates it from their panel). The secret is stored hashed on our side and cannot be shown again — store it at issue time.

API key + HMAC signature /api/v1/corporate/*  ·  /api/v1/bank/*

Sign the exact raw request body with your secret.

X-API-KEY:    <your api key>
X-TIMESTAMP:  <unix seconds>
X-SIGNATURE:  hmac_sha256( "<X-TIMESTAMP>" + "." + <raw body>, <secret> )

Sign the bytes you actually send. Serialize the JSON once, sign that exact string, and transmit that same string — re-serializing after signing (different key order or spacing) produces a signature we will reject. For a GET, the body is the empty string.

ts=$(date +%s)
body='{"account_id":42,"amount":5000000,"reference_id":"INV-1","idempotency_key":"a1b2c3"}'
sig=$(printf '%s.%s' "$ts" "$body" | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1)

curl -X POST https://hmzptest.ir/api/v1/corporate/credit/reserve \
  -H "X-API-KEY: $API_KEY" \
  -H "X-TIMESTAMP: $ts" \
  -H "X-SIGNATURE: $sig" \
  -H "Idempotency-Key: a1b2c3" \
  -H "Content-Type: application/json" \
  -d "$body"

Requests older than 300 seconds are rejected as expired_timestamp. During a secret rotation both the current and the previous secret are accepted, so you can roll over without downtime.

Every mutating corporate route additionally requires an Idempotency-Key header, and it must match the idempotency_key field in the body when the body carries one.

Session cookie /api/customer/*  ·  /api/merchant/*  ·  /api/org/*  ·  /api/admin/*

Browser-facing panel APIs — not for server-to-server use.

These authenticate with the panel session cookie established by an OTP login (password login for admin) and are CSRF-protected. They are meant for the panel front-ends. If you are integrating a system, use the POS, bank-partner or corporate API instead.

Webhook signature /api/providers/*

Inbound provider callbacks are signed the same way, with their own headers.

X-Provider-Event-Id:   <unique per event; used for deduplication>
X-Provider-Timestamp:  <unix seconds>
X-Provider-Signature:  hmac_sha256( "<timestamp>.<raw body>", <webhook secret> )

Replaying the same event id with an identical payload is safe and returns the original outcome. Replaying it with a different payload is rejected as an idempotency conflict. Bodies are capped at 64KB.

POS & Checkout API

API key + secret

Merchant point-of-sale and server-to-server checkout sessions.

Machine-to-machine, merchant API key + secret, /api/v1/pos/*

POST /api/v1/pos/orders/initiate

Start a POS credit purchase (sends an SMS confirmation code to the customer)

Machine-to-machine endpoint for a merchant's POS integration. Auth: X-API-KEY + X-API-SECRET headers, matched against the merchant's own merchant_api_keys credential (see the merchant panel's "Rotate API Key" action, or the api_credential returned by the bank-partner upsert call). Only the customer's personal bank credit account is ever considered (organization-scoped credit is a separate product and is never spent through POS). No credit is reserved at this step — it only validates eligibility and sends a one-time SMS code to the customer's own phone; the actual reservation happens atomically in /orders/confirm.

Request body PosOrderInitiateRequest
FieldTypeNotes
customer_mobilerequiredstringe.g. 09121234567
amountrequiredintegerTotal sale amount in rials.min 100000
cash_amountintegerPortion paid in cash; the remainder (amount - cash_amount) is financed on credit.min 0
installments_countrequiredintegermin 1max 12
branch_idintegernullable
Responses
201OTP sent, pending reference issuedPosOrderInitiateResponse
422Rejected. Reasons about the merchant are reported as-is (merchant_not_active, merchant_sales_cap_exceeded, invalid_cash_amount, invalid_branch). Every reason concerning the customer — no account for this mobile, account not active, insufficient credit, credit window closed — collapses into the single opaque code credit_unavailable, so this endpoint cannot be used to discover who holds credit or to binary-search a customer's available limit.ApiError
429OTP could not be sent (send rate limit or provider failure)ApiError
POST /api/v1/pos/orders/confirm

Confirm a POS purchase with the customer's SMS code (reserves and captures credit atomically)

Same auth as /orders/initiate.

Request body PosOrderConfirmRequest
FieldTypeNotes
pending_order_refrequiredstring
otp_coderequiredstring
Responses
201Credit reserved and capturedPosOrderConfirmResponse
401OTP code invalid or expiredApiError
404pending_order_ref not found, expired, or issued to a different merchantApiError
409pending_order_superseded — a later /orders/initiate for the same customer replaced this order's SMS challenge. Each code confirms only the order it was issued for; start this purchase again.ApiError
POST /api/v1/pos/checkout/sessions

Create an online-store checkout session for credit payment

Same merchant auth as the POS endpoints. Returns a checkout_url that the merchant's online store redirects the customer's browser to. The customer confirms their own mobile number and an SMS code on that hosted page before any credit is touched; this endpoint alone never reserves credit. Idempotent per idempotency_key.

Request body CheckoutSessionCreateRequest
FieldTypeNotes
amountrequiredintegermin 100000
reference_idrequiredstringThe merchant's own order/reference identifier.
customer_mobilestringOptional pre-fill; the customer still confirms it themselves on the hosted page.nullable
installments_countintegermin 1max 12
idempotency_keyrequiredstring
Responses
201New checkout session createdCheckoutSessionResponse
200Existing session returned for a repeated idempotency_keyCheckoutSessionResponse
GET /api/v1/pos/checkout/sessions/{code}

Poll an online checkout session's status

Same merchant auth as session creation.

Path parameters
NameTypeNotes
coderequiredstring
Responses
200Session statusCheckoutSessionResponse
404Session not found for this merchantApiError

Bank Partner API

API key + HMAC signature

Merchant provisioning from an external bank core system.

Machine-to-machine, API key + HMAC signature, /api/v1/bank/*

POST /api/v1/bank/merchants/upsert

Register or sync a merchant from the external bank partner ("Hamerz Bank")

Machine-to-machine endpoint. Auth: X-API-KEY header (issued via php artisan bank-partner:credentials:issue) plus HMAC request signing — X-TIMESTAMP (unix seconds) and X-SIGNATURE = HMAC-SHA256(secret, "{X-TIMESTAMP}.{raw JSON body}"), tolerance window 300s by default. A replay-safe v2 scheme is also accepted (X-SIGNATURE-VERSION: 2 + X-NONCE, signing "METHOD\nPATH\nTIMESTAMP\nNONCE\nsha256(body)") — see docs/api/README.md §2-3. external_ref is the bank's own identifier for the business and is the real dedup key — the same owner_mobile may be reused across multiple external_refs for a person who owns several businesses; each becomes a separate merchant. A new merchant is created with status: pending and cannot transact until a HamerzPay admin activates it and sets its fee rate / credit sales cap. Repeat calls with an existing external_ref only update descriptive fields and never touch status, fee_rate, or credit_sales_cap.

Request body BankPartnerMerchantUpsertRequest
FieldTypeNotes
external_refrequiredstringThe bank's own stable identifier for this business. Dedup key — never the mobile number.
owner_mobilerequiredstringe.g. 09121234567
display_namerequiredstring
legal_namestringnullable
citystringnullable
settlement_ibanstringnullable
Responses
200Existing merchant's descriptive fields were updatedBankPartnerMerchantResponse
201New merchant, owner login, and merchant API key were createdBankPartnerMerchantResponse
401Missing/invalid API key, missing/expired/invalid signatureApiError
429Rate limit exceededApiError
GET /api/v1/bank/merchants/{external_ref}

Look up a bank-provisioned merchant's status

Same auth as the upsert endpoint (API key + HMAC signature).

Path parameters
NameTypeNotes
external_refrequiredstring
Responses
200Merchant foundBankPartnerMerchantResponse
404No merchant registered for this external_ref under this credentialApiError

Corporate B2B API

API key + HMAC signature

Corporate partner credit lifecycle: check, reserve, capture, cancel, settle.

Machine-to-machine, API key + HMAC signature, /api/v1/corporate/*

GET /api/v1/corporate/company

Authenticated company's profile and facility summary

Responses
200Company + facilityCorporateApiSuccess
GET /api/v1/corporate/credentials/status

Status of the calling API credential (masked key, is_active, created_at)

Responses
200Credential statusCorporateApiSuccess
GET /api/v1/corporate/plans

List the company's active credit plans (read-only)

Responses
200Plan listCorporateApiSuccess
GET /api/v1/corporate/customers

List/search the company's customers (paginated)

Query parameters
NameTypeNotes
statusstring
mobilestring
national_idstring
updated_sincestringdate-time
per_pageintegermin 1max 100
Responses
200Customer listCorporateApiSuccess
POST /api/v1/corporate/customers/upsert

Create or update a customer by external_ref

Requires the Idempotency-Key header.

Required headers
NameTypeNotes
Idempotency-Keyrequiredstring
Request body
FieldTypeNotes
external_refrequiredstringmaxLen 80
namerequiredstringmaxLen 160
mobilestringmaxLen 20nullable
national_idstringmaxLen 20nullable
emailstringemailnullable
statusstringactive | suspendednullable
metadataobjectnullable
Responses
200Existing customer updated
201New customer created
GET /api/v1/corporate/customers/{external_ref}

Get one customer by external_ref

Path parameters
NameTypeNotes
external_refrequiredstring
Responses
200Customer found
404Not found (or belongs to another company)CorporateApiError
GET /api/v1/corporate/customers/{external_ref}/credit

Customer's corporate credit accounts + facility summary

Path parameters
NameTypeNotes
external_refrequiredstring
Responses
200Accounts and facility
GET /api/v1/corporate/customers/{external_ref}/wallets

Eligible corporate wallets for a prospective purchase amount

Path parameters
NameTypeNotes
external_refrequiredstring
Query parameters
NameTypeNotes
amountrequiredintegermin 1
merchant_idinteger
Responses
200Wallet eligibility list
GET /api/v1/corporate/customers/{external_ref}/documents

List a customer's uploaded documents

Path parameters
NameTypeNotes
external_refrequiredstring
Responses
200Document list
POST /api/v1/corporate/customers/{external_ref}/documents

Upload a customer document (multipart file, or base64 JSON payload)

Requires the Idempotency-Key header. Max 10MB; pdf/jpg/jpeg/png/webp only.

Path parameters
NameTypeNotes
external_refrequiredstring
Required headers
NameTypeNotes
Idempotency-Keyrequiredstring
Request body
FieldTypeNotes
document_typerequiredstringmaxLen 120
titlestringnullable
file_content_base64stringnullable
original_filenamestringnullable
mime_typestringapplication/pdf | image/jpeg | image/png | image/webpnullable
metadataobjectnullable
Responses
201Document stored
GET /api/v1/corporate/customers/{external_ref}/documents/{document}

Get one customer document's metadata

Path parameters
NameTypeNotes
external_refrequiredstring
documentrequiredinteger
Responses
200Document metadata
POST /api/v1/corporate/customers/{external_ref}/credit-accounts/request

Request a new credit account for a customer (pending or needs_review)

Requires the Idempotency-Key header.

Path parameters
NameTypeNotes
external_refrequiredstring
Required headers
NameTypeNotes
Idempotency-Keyrequiredstring
Request body
FieldTypeNotes
plan_idintegernullable
credit_limitrequiredintegermin 1
settlement_daysintegermin 1max 365nullable
reasonstringnullable
allowed_merchant_idsinteger[]
metadataobject
Responses
201Account request created
POST /api/v1/corporate/customers/{external_ref}/credit-accounts/activate

Activate a credit account immediately (or flag needs_review)

Requires the Idempotency-Key header.

Path parameters
NameTypeNotes
external_refrequiredstring
Required headers
NameTypeNotes
Idempotency-Keyrequiredstring
Request body
FieldTypeNotes
plan_idrequiredinteger
credit_limitrequiredintegermin 1
settlement_daysintegernullable
allowed_merchant_idsinteger[]
metadataobject
Responses
201Account activated
202Flagged needs_review instead (facility exceeded or settlement days exceed plan)
POST /api/v1/corporate/customers/{external_ref}/credit-limit/request

Request a credit-limit change for an existing account

Requires the Idempotency-Key header.

Path parameters
NameTypeNotes
external_refrequiredstring
Required headers
NameTypeNotes
Idempotency-Keyrequiredstring
Request body
FieldTypeNotes
account_idrequiredinteger
requested_credit_limitrequiredintegermin 0
reasonrequiredstringmaxLen 1000
effective_atstringdate-timenullable
Responses
201Limit-change request created (pending or needs_review)
POST /api/v1/corporate/settlement-requests

Submit a self-reported settlement

Requires the Idempotency-Key header.

Required headers
NameTypeNotes
Idempotency-Keyrequiredstring
Request body
FieldTypeNotes
external_refrequiredstring
account_idrequiredinteger
usage_idintegernullable
amountrequiredintegermin 1
reference_numberrequiredstringmaxLen 120
paid_atstringdate-timenullable
notesstringnullable
Responses
201Settlement request recorded
GET /api/v1/corporate/settlement-requests/{settlementRequest}

Get one settlement request's status

Path parameters
NameTypeNotes
settlementRequestrequiredinteger
Responses
200Settlement request
POST /api/v1/corporate/webhooks/test

Dispatch a test webhook event to all of the company's active endpoints

Request body
FieldTypeNotes
eventstringmaxLen 120nullable
messagestringmaxLen 255nullable
Responses
201Test event dispatched
POST /api/v1/corporate/credit/check

Check corporate-credit eligibility (tenant-scoped by account_id)

Request body
FieldTypeNotes
account_idrequiredintegermin 1
amountrequiredintegermin 1
Responses
200Eligibility result
POST /api/v1/corporate/credit/reserve

Reserve corporate credit for a usage

Requires the Idempotency-Key header; account_id is tenant-scoped.

Required headers
NameTypeNotes
Idempotency-Keyrequiredstring
Request body
FieldTypeNotes
account_idrequiredintegermin 1
amountrequiredintegermin 1
reference_idrequiredstringmaxLen 255
idempotency_keyrequiredstringmaxLen 255
repayment_daysintegermin 1max 365nullable
due_atstringdate-timenullable
repayment_policystringnullable
invoice_numberstringnullable
invoice_datestringdatenullable
metadataobject
Responses
201Usage reserved
POST /api/v1/corporate/credit/capture

Capture a reserved corporate-credit usage

Requires the Idempotency-Key header; usage_id is tenant-scoped.

Required headers
NameTypeNotes
Idempotency-Keyrequiredstring
Request body
FieldTypeNotes
usage_idrequiredintegermin 1
idempotency_keyrequiredstringmaxLen 255
repayment_scheduleobject[]
Responses
200Usage captured
POST /api/v1/corporate/credit/cancel

Cancel a reserved (not yet captured) corporate-credit usage

Requires the Idempotency-Key header; usage_id is tenant-scoped.

Required headers
NameTypeNotes
Idempotency-Keyrequiredstring
Request body
FieldTypeNotes
usage_idrequiredintegermin 1
idempotency_keyrequiredstringmaxLen 255
Responses
200Usage cancelled
POST /api/v1/corporate/credit/settle

Record a settlement against a captured corporate-credit usage

Requires the Idempotency-Key header; usage_id is tenant-scoped.

Required headers
NameTypeNotes
Idempotency-Keyrequiredstring
Request body
FieldTypeNotes
usage_idrequiredintegermin 1
amountrequiredintegermin 1
reference_numberrequiredstringmaxLen 255
idempotency_keyrequiredstringmaxLen 255
Responses
201Settlement recorded
GET /api/v1/corporate/credit-lines

List the company's revolving credit lines (Unified Credit Core) with utilization and collateral coverage

Read-only. Each line carries approved_limit, consumed/reserved/utilized/available amounts, eligible_collateral_value (sum of ACTIVE, unexpired collateral eligible values), collateral_backed_max_credit (= eligible × LTV) and within_collateral.

Query parameters
NameTypeNotes
statusstringACTIVE | SUSPENDED | CLOSED
external_refstringFilter by customer external_ref
Responses
200Credit lines + ltv_percentCorporateApiSuccess
GET /api/v1/corporate/collaterals

List the company's collateral records (paginated)

Query parameters
NameTypeNotes
statusstringPENDING | ACTIVE | EXPIRED | RELEASED | CALLED
credit_line_idinteger
external_refstring
per_pageintegermin 1max 100
Responses
200Collateral list + paginationCorporateApiSuccess
POST /api/v1/corporate/collaterals

Register a collateral for one of the company's customers (created as PENDING; approval is done by HamerzPay)

Requires the Idempotency-Key header; the same key for the same customer replays the original record (200, replayed=true). credit_line_id is tenant-scoped: another company's line answers 404. eligible_value = nominal_value × (1 − haircut_percentage/100).

Required headers
NameTypeNotes
Idempotency-Keyrequiredstring
Request body
FieldTypeNotes
external_refrequiredstringmaxLen 80
credit_line_idintegerMust belong to the same customernullable
typerequiredstringCHEQUE | PROMISSORY_NOTE | BANK_GUARANTEE | CASH_DEPOSIT | CONTRACTUAL | OTHER
nominal_valuerequiredintegermin 1
haircut_percentagenumbermin 0max 100
reference_numberstringmaxLen 100nullable
issue_datestringdatenullable
expiry_datestringdatenullable
Responses
201Collateral registered (PENDING)
200Idempotent replay of an earlier registration
404Unknown customer or another company's credit lineCorporateApiError
422Validation failed or Idempotency-Key missing
GET /api/v1/corporate/receivables

List the company's per-purchase receivables (paginated)

Query parameters
NameTypeNotes
statusstringOPEN | PARTIALLY_PAID | PAID | OVERDUE | DEFAULTED | WRITTEN_OFF
fromstringissue_date >= fromdate
tostringissue_date <= todate
due_fromstringdue_date >= due_fromdate
due_tostringdue_date <= due_todate
credit_line_idinteger
external_refstring
per_pageintegermin 1max 100
Responses
200Receivables (principalCorporateApiSuccess
POST /api/v1/corporate/receivables/{receivable}/payments

Register a (partial or full) payment against a receivable

Requires the Idempotency-Key header, unique per company: the same key replays the original payment (200, replayed=true) without deducting again; the same key with a different amount/receivable is rejected (422 corporate_credit_rule_violation). Over-payment and payments on PAID/WRITTEN_OFF receivables are rejected with 422. The receivable is tenant-scoped: another company's id answers 404. Paid principal frees the revolving line's limit; fees never do.

Path parameters
NameTypeNotes
receivablerequiredinteger
Required headers
NameTypeNotes
Idempotency-Keyrequiredstring
Request body
FieldTypeNotes
amountrequiredintegermin 1
reference_numberstringmaxLen 120nullable
Responses
201Payment registered; returns receivable
200Idempotent replay; replayed=true
404Receivable not found or belongs to another companyCorporateApiError
422Rule violation (over-payment

Hosted Checkout Page

Public

The customer-facing page your checkout session redirects to.

Public, customer-facing hosted page, /checkout/*

GET /checkout/{code}

Hosted online-checkout page (customer-facing, not machine-callable)

Public HTML page (session + CSRF protected, throttled 20/min/IP), not part of the JSON API surface. The customer enters/confirms their mobile number, receives an SMS code, and confirms it here — the page then reserves and captures credit atomically and redirects to the merchant's callback_url (configured in merchant_settings), or shows a success message if none is configured. Returns 404 for an unknown code, 410 for an expired/cancelled session, 409 if the session was already completed.

Path parameters
NameTypeNotes
coderequiredstring
Responses
200Checkout page HTML
POST /checkout/{code}/otp/send

Submit the customer's mobile number and trigger an SMS code

If the merchant pinned customer_mobile when creating the session, only that number is accepted; any other number is refused with an error flash rather than silently repointing the session.

Path parameters
NameTypeNotes
coderequiredstring
Request body application/x-www-form-urlencoded
FieldTypeNotes
mobilerequiredstringe.g. 09121234567
Responses
302Redirect back to the checkout page with a success or error flash message
POST /checkout/{code}/otp/verify

Confirm the SMS code and complete the purchase (reserve + capture, atomic)

Path parameters
NameTypeNotes
coderequiredstring
Request body application/x-www-form-urlencoded
FieldTypeNotes
otp_coderequiredstring
Responses
302On success, redirects to the merchant's callback_url (?code&status=success) or back to the checkout page with a success message. On failure, redirects back to the checkout page with an error flash message.

Provider Webhooks

HMAC signature (shared secret)

Inbound financial events you send us.

Inbound provider callbacks, HMAC-signed (not API key), /api/providers/*

POST /api/providers/{provider}/financial-events

Inbound financial event callback from a payment/settlement/funding/payroll provider

Verified via HMAC-SHA256 over "{X-Provider-Timestamp}.{raw body}" using hamerzpay.finance.providers.{provider}.webhook_secret (with previous_webhook_secret accepted during rotation), not an API key. A 300-second freshness window applies and the raw body is capped at 64KB. Deduplicated by (provider, X-Provider-Event-Id) via the Inbox pattern — replays with the same event id and a different payload are rejected as an idempotency conflict.

Path parameters
NameTypeNotes
providerrequiredstringpayment | settlement | funding | payroll
Required headers
NameTypeNotes
X-Provider-Event-Idrequiredstring
X-Provider-TimestamprequiredstringUnix seconds
X-Provider-Signaturerequiredstring
Request body

Provider-defined payload; must include event_type.

Responses
202Event accepted into the inbox and processed
401Invalid signature (event is still recorded, marked invalid)
413Payload exceeds 64KB
422Missing event id, timestamp, or signature header

Customer Panel API

Session cookie (OTP / password login)

Session-authenticated (OTP login), /api/customer/*

GET /api/customer/health

Panel health check

Responses
200Panel is healthyPanelHealth
GET /api/customer/credit/summary

Authenticated customer's own credit summary

Responses
200Credit snapshot
POST /api/customer/credit/check

Check purchase eligibility against the customer's own credit account

Request body
FieldTypeNotes
account_idrequiredinteger
amountrequiredintegermin 1
merchant_idintegernullable
categorystringnullable
Responses
200Eligibility resultCreditCheckResult
403account_id does not belong to the authenticated customer
POST /api/customer/credit/reserve

Reserve credit for a purchase (step 1 of 2)

Request body
FieldTypeNotes
account_idrequiredinteger
amountrequiredintegermin 1
installments_countrequiredintegermin 1max 12
merchant_idintegernullable
idempotency_keyrequiredstringmaxLen 200
Responses
201Order reserved (or the existing order for a repeated idempotency_key)CreditOrderSummary
POST /api/customer/credit/{order}/capture

Capture (finalize) a reserved order — step 2 of 2

Path parameters
NameTypeNotes
orderrequiredinteger
Request body
FieldTypeNotes
idempotency_keyrequiredstringmaxLen 200
Responses
200Order capturedCreditOrderSummary
POST /api/customer/credit/{order}/cancel

Cancel a reservation before it is captured

Path parameters
NameTypeNotes
orderrequiredinteger
Request body
FieldTypeNotes
idempotency_keyrequiredstringmaxLen 200
Responses
200Order cancelled
POST /api/customer/credit/evaluate

Request a bank-credit underwriting decision

Request body
FieldTypeNotes
requested_amountrequiredintegermin 10000
requested_installmentsrequiredintegermin 1max 12
Responses
200Underwriting decision (approved / rejected / needs_guarantor / needs_collateral)
POST /api/customer/credit/activate

Activate a previously approved credit request

Request body
FieldTypeNotes
request_idrequiredinteger
Responses
200Bank credit account activated with the approved limit
GET /api/customer/credit/orders

List the authenticated customer's own credit orders (paginated)

Responses
200Paginated order list
GET /api/customer/credit/installments

List the authenticated customer's own installments, with overdue/upcoming breakdown

Responses
200Installment summary and list
POST /api/customer/credit/installments/{installment}/pay

Submit a repayment for one installment

Requires the Idempotency-Key header (not a body field).

Path parameters
NameTypeNotes
installmentrequiredinteger
Required headers
NameTypeNotes
Idempotency-Keyrequiredstring
Request body
FieldTypeNotes
amountrequiredintegermin 1
Responses
202Repayment operation accepted (processed asynchronously)

Merchant Panel API

Session cookie (OTP / password login)

Session-authenticated (OTP login), /api/merchant/*

GET /api/merchant/health

Panel health check

Responses
200Panel is healthyPanelHealth
POST /api/merchant/checkout/customers/{customerId}/otp

Send a purchase-confirmation SMS code to a corporate customer

Step 1 of corporate-wallet checkout. The code goes to the corporate customer's own mobile; the customer reads it to the cashier in person. Rate limits and lockouts are the shared OTP policy. Requires merchant role owner/manager/branch_manager/cashier (merchant.corporate-checkout.create).

Path parameters
NameTypeNotes
customerIdrequiredinteger
Responses
202Code sent; data.customer_id, data.mobile_masked
403Operator role not allowed
404CUSTOMER_NOT_FOUND
429OTP_SEND_FAILED (cooldown / rate limit / provider)
POST /api/merchant/checkout/customers/{customerId}/otp/verify

Exchange the customer's SMS code for a short-lived checkout_token

Step 2. The token is bound to (this merchant, this customer), lives 10 minutes, and authorises exactly one checkout (one idempotency_key; retries with the same key are allowed).

Path parameters
NameTypeNotes
customerIdrequiredinteger
Request body
FieldTypeNotes
otp_coderequiredstringmaxLen 10
Responses
200data.checkout_token, data.expires_at, data.customer_id
422OTP_INVALID (wrong, expired, or issued for another merchant's request)
POST /api/merchant/checkout/session

Corporate-wallet checkout (check + reserve + capture in one call)

For paying with a customer's *corporate* wallet credit specifically — unrelated to the bank-credit online checkout under /api/v1/pos/checkout. Contract change 2026-10-03: customer_id and checkout_token (from /otp/verify) are now required, and corporate_account_id must belong to that customer. The company must also explicitly allow this merchant (corporate_allowed_merchants); a company with no rows is now denied unless CORPORATE_ALLOW_ALL_MERCHANTS_WHEN_UNRESTRICTED=true.

Request body
FieldTypeNotes
customer_idrequiredintegermin 1
checkout_tokenrequiredstringmaxLen 128
corporate_account_idrequiredintegermin 1
amountrequiredintegermin 1
reference_idrequiredstringmaxLen 191
idempotency_keyrequiredstringmaxLen 191
metadataobject
Responses
201Corporate wallet checkout completed
403CUSTOMER_VERIFICATION_REQUIRED (missing/expired/foreign token) or operator role not allowed
409CHECKOUT_TOKEN_USED — token already spent on a different idempotency_key
422Checkout rejected (e.g. insufficient corporate credit, account not owned by customer, merchant not allowed)ApiError
GET /api/merchant/checkout/customers/{customerId}/wallets

List a customer's eligible corporate wallets for a prospective purchase

Contract change 2026-10-03: requires a checkout_token for this customer (query param or X-Checkout-Token header) obtained via /otp/verify.

Path parameters
NameTypeNotes
customerIdrequiredinteger
Query parameters
NameTypeNotes
merchant_idrequiredinteger
amountrequiredintegermin 1
checkout_tokenstringRequired unless sent as the X-Checkout-Token header
Responses
200Eligible wallets, with auto_select_wallet_id when exactly one qualifies
403MERCHANT_MISMATCH, CUSTOMER_VERIFICATION_REQUIRED, or operator role not allowed

Organization Panel API

Session cookie (OTP / password login)

Session-authenticated (OTP login), /api/org/*

GET /api/org/health

Panel health check

Responses
200Panel is healthyPanelHealth
GET /api/org/credit/summary

Organization-wide credit/budget summary

Responses
200Budget snapshotBudgetSummary
GET /api/org/budget/summary

Alias of /credit/summary (legacy path kept for backward compatibility)

Responses
200Budget snapshotBudgetSummary
POST /api/org/credit/check

Check purchase eligibility against an organization-scoped credit account

Request body
FieldTypeNotes
account_idrequiredinteger
amountrequiredintegermin 1
merchant_idintegernullable
categorystringnullable
Responses
200Eligibility resultCreditCheckResult
POST /api/org/credit/allocate

Allocate/update a member's credit limit

Caller must be owner, admin, manager, or a finance/HR operator of the organization. The organization is taken from the authenticated panel session; member_id and policy_id must both belong to it.

Request body
FieldTypeNotes
member_idrequiredinteger
credit_limitrequiredintegermin 0
policy_idintegernullable
idempotency_keyrequiredstringRequired. Replaying the same key returns the original allocation instead of posting a second ledger entry.maxLen 200
Responses
200Allocation applied
403Member or policy belongs to a different organizationApiError
POST /api/org/allocations/reduce

Reduce a member's allocation (owner/admin/finance-operator only, strictly a decrease)

Request body
FieldTypeNotes
member_idrequiredinteger
new_limitrequiredintegerMust be strictly lower than the member's current credit_limit.min 0
idempotency_keyrequiredstringmaxLen 191
Responses
200Allocation reduced
422new_limit is not lower than the current allocation
GET /api/org/credit/members

List organization members and their credit status

Responses
200Member list
POST /api/org/credit/members/create

Add a new member with an initial credit allocation (one-shot onboarding)

Request body
FieldTypeNotes
mobilerequiredstring
namerequiredstringminLen 2maxLen 100
credit_limitrequiredintegermin 0
rolestringemployee | manager | unit_manager
policy_idintegernullable
employee_codestringnullable
position_titlestringnullable
unit_idintegernullable
branch_idintegernullable
Responses
201Member and credit account created
GET /api/org/credit/policies

List the organization's credit policies

Responses
200Policy list

Admin Panel API

Session cookie (OTP / password login)

Session-authenticated (password login), /api/admin/*

GET /api/admin/health

Panel health check

Responses
200Panel is healthyPanelHealth
GET /api/admin/kpi/summary

Admin KPI summary

Responses
200KPI snapshotKpiSummary
GET /api/admin/corporate/settlements/reconciliation

Corporate settlement reconciliation report (read-only)

Requires the admin.corporate.data.view ability.

Query parameters
NameTypeNotes
company_idinteger
merchant_idinteger
fromstringdate
tostringdate
statusstring
Responses
200Reconciliation report
GET /api/admin/corporate/risk/overdue

Corporate overdue-risk report (read-only)

Requires the admin.corporate.data.view ability.

Query parameters
NameTypeNotes
company_idinteger
customer_idinteger
statusstring
min_overdue_amountinteger
fromstringdate
tostringdate
Responses
200Overdue items, count, and total_overdue_amount
GET /api/admin/corporate/operations/summary

Corporate production-operations health summary (locked accounts, failed settlements/webhooks, queue backlog)

Requires the admin.corporate.data.view ability.

Responses
200Operations summary with an overall status of ok/warning/critical
POST /api/admin/corporate/accounts/{account}/{action}

Lock, unlock, suspend, or close a corporate credit account

Requires the admin.corporate.account.manage ability.

Path parameters
NameTypeNotes
accountrequiredinteger
actionrequiredstringlock | unlock | suspend | close
Request body
FieldTypeNotes
reasonrequiredstringminLen 3maxLen 500
Responses
200Account status changed
POST /api/admin/corporate/webhooks/{delivery}/retry

Manually retry a failed corporate webhook delivery

Requires the admin.corporate.webhook.manage ability.

Path parameters
NameTypeNotes
deliveryrequiredinteger
Request body
FieldTypeNotes
reasonrequiredstringminLen 3maxLen 500
Responses
200Retry scheduled/attempted
POST /api/admin/corporate/customer-documents/{document}/review

Approve or reject a corporate customer's uploaded document

Requires the admin.corporate.customer.manage ability. Triggers a customer.document.{approved,rejected} partner webhook.

Path parameters
NameTypeNotes
documentrequiredinteger
Request body
FieldTypeNotes
statusrequiredstringapproved | rejected
rejection_reasonstringRequired when status=rejected.nullable
Responses
200Document reviewed
POST /api/admin/corporate/settlements/{settlement}/mark-failed

Mark a corporate settlement as failed

Requires the admin.corporate.settlement.manage ability.

Path parameters
NameTypeNotes
settlementrequiredinteger
Request body
FieldTypeNotes
reasonrequiredstringminLen 3maxLen 500
Responses
200Settlement marked failed
POST /api/admin/corporate/settlements/{settlement}/retry-processing

Retry processing a corporate settlement

Requires the admin.corporate.settlement.manage ability.

Path parameters
NameTypeNotes
settlementrequiredinteger
Request body
FieldTypeNotes
reasonrequiredstringminLen 3maxLen 500
Responses
200Settlement processing retried