A member generates a 10-character code + 7-character PIN; a merchant redeems it against an amount. Lockout-protected, single-use by default, can be merchant-locked or recurring.
Issuing.Authorizations row, so codes land in the same ledger as card authorizations.
POST /v1/payment-codes ↓ [Code Active] — 10-min expiry by default ↓ POST /authorize (correct PIN) [Code Used] — single-use codes + Issuing.Authorizations row written ↓ POST /authorize (wrong PIN, < lockoutThreshold) [Code Active] but Attempts++ ↓ POST /authorize (wrong PIN, >= lockoutThreshold) [Code Locked] — cannot be redeemed without revoke + reissue ↓ POST /<code>/revoke [Code Revoked] — terminal
Mint a payment code on behalf of a member. The response returns code + pin — the PIN appears only once. Surface both to the member immediately; we don’t store the plaintext.
| Field | Type | Description |
|---|---|---|
| member required | string | Relationship.Accounts.RecordId — whose account funds the redemption. |
| account optional | string | Specific Financial.Accounts.RecordId to debit. Defaults to the member’s primary. |
| merchant optional | string | Lock to one Merchant.Accounts.RecordId. Other merchants get a decline. |
| maxAmount optional | number | Per-redemption cap. Redemptions over this are declined. |
| currency optional | string | Core.Currencies.RecordId. USD default. |
| expiryMinutes optional | integer | Default 10. |
| singleUse optional | boolean | Default true. Mutually exclusive with allowRecurring. |
| allowRecurring optional | boolean | Default false. When true, set recurringInterval + recurringLimit. |
| recurringInterval optional | enum | daily / weekly / monthly |
| recurringLimit optional | integer | Max successful uses across the recurring lifetime. |
| recurringThru optional | date | End date for recurring codes. |
| lockoutThreshold optional | integer | Bad-PIN attempts before the code is locked. Default 5. |
| displayHint optional | string | UI label the member sees alongside the code. |
| metadata optional | object | JSON passthrough. |
curl https://api.rmous.org/v1/payment-codes \ -H "X-API-Key: pk_test_publicsandbox_2026" \ -H "Authorization: Bearer sk_test_publicsandbox_2026_anyone_can_use_this" \ -H "Idempotency-Key: mint-2026-05-14-001" \ -H "Content-Type: application/json" \ -d '{ "member": "MEMBER_RECORD_ID", "maxAmount": 50.00, "merchant": "MERCHANT_RECORD_ID", "singleUse": true, "expiryMinutes": 15, "displayHint":"For tonight's pickup at RMO Coffee" }' # Response (201) - PIN returned ONCE: { "recordId": "PcRRdY1mQXz3VnaFx", "code": "A7BX3FQM2N", // 10 chars (Crockford) "pin": "9KQVZD2", // 7 chars - returned ONCE "expiresAt": "2026-05-14T17:08:11Z", "status": "Code Active" }
Codes and PINs are drawn from the Crockford base-32 alphabet (0-9, A-Z without I, L, O, U). Safe to dictate over the phone, hard to misread.
Merchant submits code + pin + amount. On success, an Issuing.Authorizations row is written and the code transitions to Code Used (if single-use). On wrong PIN, Attempts increments and the code locks once lockoutThreshold is reached.
| Field | Type | Description |
|---|---|---|
| code required | string(10) | The code the member shows. |
| pin required | string(7) | The PIN the member provides verbally / on a keypad. |
| amount required | number | Positive number. |
| merchant required | string | Merchant.Accounts.RecordId. Must match the code’s lock if one is set. |
| currency optional | string | USD default. |
| deviceFingerprint optional | string | Audit signal — helps with fraud analysis. |
| metadata optional | object | JSON passthrough. |
curl https://api.rmous.org/v1/payment-codes/authorize \ -H "X-API-Key: $RMO_API_KEY" \ -H "Authorization: Bearer $RMO_BEARER" \ -H "Idempotency-Key: redeem-A7BX3FQM2N-1" \ -H "Content-Type: application/json" \ -d '{ "code": "A7BX3FQM2N", "pin": "9KQVZD2", "amount": 42.50, "merchant": "MERCHANT_RECORD_ID" }' # Approved (201): { "status": "approved", "authorizationId": "7421594", "recordId": "R1d2C3o4D5e6P7i8N", "amount": 42.50, "member": "MEMBER_RECORD_ID", "expires": "2026-05-21T17:08:11Z" } # Declined (200) - wrong PIN, lockout countdown: { "status": "declined", "reasonCode": "75", "reasonMessage": "Invalid PIN", "amount": 42.50 }
Other declines: 54 code expired, 61 amount over maxAmount, 62 code revoked / locked to a different merchant, 75 code locked.
Returns the code’s current state. The PIN is never returned, regardless of which tenant asks.
curl https://api.rmous.org/v1/payment-codes/A7BX3FQM2N \ -H "X-API-Key: $RMO_API_KEY" \ -H "Authorization: Bearer $RMO_BEARER"
Permanently revoke a code. Useful when a member loses access to their PIN, or you suspect compromise. Already-redeemed codes can’t be un-redeemed — this only prevents future use.
curl -X POST https://api.rmous.org/v1/payment-codes/A7BX3FQM2N/revoke \ -H "X-API-Key: $RMO_API_KEY" \ -H "Authorization: Bearer $RMO_BEARER" \ -H "Content-Type: application/json" \ -d '{"reason": "member requested cancellation"}' # Response: { "revoked": true, "recordId": "PcRRdY1mQXz3VnaFx" }