API Documentation

Base URL: https://api.paycircle.site. All responses are JSON. Endpoints marked πŸ”’ require Authorization: Bearer <token> from the verify or login call.

Authentication

OTP-first accounts

POST /auth/registeremail, phone. Sends a 6-digit WhatsApp/SMS code. Calling again for an existing account resends the code.
POST /auth/verifyemail, phone, otp. Activates the account, returns { token } (JWT, 7-day expiry).
POST /auth/loginemail, password. For accounts registered with a password.
# register then verify
POST /auth/register  { "email": "you@corp.com", "phone": "+254700000000" }
POST /auth/verify    { "email": "...", "phone": "...", "otp": "123456" }
β†’ { "token": "eyJhbGci...", "activated": true }
Sandbox notice: while OTP_DEV_MODE=true, register and resend responses include dev_code so you can self-complete verification. This field disappears in production OTP delivery.

Membership

Trial, cooling, activation β€” Β£10/month

30-day free trial β†’ 3-day cooling period β†’ Β£10/month charged in advance. Cancel anytime before billing. The 1% payout fee applies from day one.

POST /membership/signupemail. Starts the trial clock. Idempotent.
GET /membership?email=Returns derived status (trial | cooling | active | cancelled | none), days_left, reference, renewal date.
POST /membership/cancelemail. Cancels; no further membership fees.
POST /membership/payment πŸ”email + header X-Admin-Key. Operator confirms fee receipt β†’ activates until next renewal (+30 days).
GET /membership?email=you@corp.com
β†’ { "status": "trial", "days_left": 27, "reference": "PC-A0A96F59", ... }

Hosted at https://membership-status-paycircle.fbirele.workers.dev. The dashboard polls this every 10 seconds.

Deposits

Funding your treasury β€” USD, minimum $250

POST /suby/deposit πŸ”’amount_usd β‰₯ 250. Returns a unique reference (PC-DEP-######) and payment instructions. Status starts pending.
GET /suby/deposits πŸ”Operator list of all deposits.
POST /suby/deposit/received/:id πŸ”Operator confirms funds landed β†’ credits treasury balance. Optional gbp_value.
POST /suby/deposit   { "amount_usd": 6000 }
β†’ { "deposit_id": 9, "reference": "PC-DEP-000009",
     "status": "pending", "instructions": "..." }

Bank rails: Revolut Business β€” sort 23-01-20 Β· account 74276923 Β· IBAN GB20 REVO 2301 2074 2769 23 Β· BIC REVOGB21. Full details at /bank-details. Always quote your reference.

Transfers & fees

Payouts with a flat 1% fee

POST /transfer/send πŸ”’amount, recipient. Fee = amount Γ— 1%. Response includes { status, id, amount, fee, total }.
POST /transfer/send   { "amount": 1000, "recipient": "supplier-ke-01" }
β†’ { "status": "initiated", "id": "po_x", "amount": 1000,
     "fee": 10, "total": 1010 }
POC phase: minimum transfer $250, processed manually by the operations desk. Email help@paycircle.me with amount, recipient, and reference. The endpoint returns a clean 503 naming the missing rail configuration until payout keys are live.

Treasury sweep

Balance β†’ Loadcircle Revolut account

GET /margin/status πŸ”’Treasury balance, Β£10,000 sweep threshold, eligibility, rail configuration flag.
POST /margin/sweep πŸ”’amount in GBP. Rules per infrastructure spec: minimum Β£1,000, maximum Β£50,000 per transaction, triggered above Β£10,000 balance.
GET /margin/status
β†’ { "balance": 4700, "sweep_threshold": 10000,
     "eligible": false, "rail_configured": false }

Support β€” Hermes

Deterministic first line, human escalation

POST /support/chat πŸ”’message. Instant answer for known topics (OTP, payouts, membership, rates, bank details). Unknown questions open a ticket automatically.
POST /support/ticket πŸ”’subject, body. Creates a tracked ticket.
POST /support/escalate πŸ”’ticket_id. Applies the ladder: Kenya call centre after 24h, UK after 48h.
GET /support/status/:id πŸ”’Ticket status and channel.
POST /support/chat   { "message": "i never got my otp" }
β†’ { "channel": "hermes", "resolved": true, "reply": "Codes expire after 10 minutes..." }

Live FX

Rate board β€” public, refreshes every 10 seconds

GET /rates/liveNo auth. Corridor FX (USD→KES, GBP→NGN, EUR→GHS), rail fees, PayCircle commission, total cost per corridor, timestamp. Cached 30s server-side.
GET https://paycircle.site/rates/live
β†’ { "fx": { "USD_KES": 129.42, ... },
     "rails": { "mpesa": 0.015, ... },
     "paycircle": { "commission_rate": 0.01, "monthly_fee": 10 },
     "total_cost": { "USD_KES": 0.025, ... },
     "timestamp": "2026-08-22T21:52:40.028Z" }

Errors

Clean failures only

400Validation β€” below minimums, missing fields. Message states the rule.
401Missing/invalid Bearer token, bad signature, bad operator key.
503Rail not configured β€” response names the exact missing environment variables.
Webhooks: inbound Suby events are accepted only with an x-suby-signature HMAC-SHA256 header over the raw body. Unsigned requests are rejected at the edge. Endpoints: /webhooks/suby/merchant and /webhooks/suby/payout.