API and data reference
Every P2P route, permission key, setting key, database table, cron job, wallet operation type and escrow idempotency key, in one place.
Lookup material. Nothing here is a how-to — see the guides for that.
Everything under /api/p2p and /api/admin/p2p sits behind the extension
licence gate for p2p. Until the licence validates, every route on this page
fails regardless of authentication.
Public routes
Unauthenticated. Guests can read the market when p2pAllowGuestBrowsing is on —
and, because these endpoints do not consult that setting, when it is off too.
Offers
Trades
Payment methods, wallets and dashboard
Admin routes
Offers
Trades
Disputes
Payment methods
Permission keys
Twelve. They follow the platform convention — the admin path with / becoming
. and - becoming _.
access.p2p
view.p2p.trade edit.p2p.trade
view.p2p.offer edit.p2p.offer
view.p2p.dispute edit.p2p.dispute
view.p2p.activity
view.p2p.payment_method create.p2p.payment_method
edit.p2p.payment_method delete.p2p.payment_methodSetting keys
| Key | Type | Default | Enforced |
|---|---|---|---|
p2pDefaultPaymentWindow |
number (minutes) | 15 | server |
p2pAutoCancelUnpaidTrades |
switch | true | server (cron) |
p2pDisputeResolutionHours |
number (0 = no target) | 48 | browser only |
p2pRequirePaymentReference |
switch | true | server |
p2pMaxActiveTrades |
number (0 = no limit) | 0 | server |
p2pMinimumTradeAmount |
number (USD) | 10 | server |
p2pMaximumTradeAmount |
number (USD) | 100000 | server |
p2pEscrowFeeRate |
percentage | 0.2 | server |
p2pMinimumTradeAmounts |
map | per-currency floors | server |
p2pAutoApproveOffers |
switch | false | server |
p2pEnabled |
switch | true | browser only |
p2pMaintenanceMode |
switch | false | browser only |
p2pAllowNewOffers |
switch | true | browser only |
p2pAllowGuestBrowsing |
switch | true | browser only |
p2pMarketFilterStyle |
guided | classic |
guided | browser |
p2pDefaultMarketLens |
select | match | no consumer |
p2pShowTradersLens |
switch | true | no consumer |
p2pFeaturedPicksCount |
number | 3 | no consumer |
p2pDetectVisitorLocale |
switch | true | no consumer |
A legacy settings row keyed p2p takes precedence over p2pEscrowFeeRate and
p2pMinimumTradeAmounts where it exists — see
Settings.
The two trade-amount bounds are US dollars and are converted into an offer's
pricing currency before any comparison, at the same fiat rates the rest of the
platform uses. GET /api/p2p/offer/[id] returns them already converted as
platformLimits: { min, max, currency }, so a client never has to do the
arithmetic — and a signed-out visitor, who cannot reach the rate endpoint, still
sees the real bound. Either field is null when that bound is unconfigured or
when no rate exists to convert it with.
Tables
| Table | Model | Holds |
|---|---|---|
p2p_offers |
p2pOffer |
offers, five JSON config columns, escrowAmount, views, systemTags, adminNotes, activityLog |
p2p_trades |
p2pTrade |
trades, escrowAmount, escrowStatus, timeline, paymentDetails, resolution, lifecycle timestamps |
p2p_disputes |
p2pDispute |
disputes, evidence, messages, resolution, activityLog |
p2p_payment_methods |
p2pPaymentMethod |
global (userId null) and personal methods, metadata |
p2p_offer_payment_method |
p2pOfferPaymentMethod |
the offer ↔ method join |
p2p_offer_flags |
p2pOfferFlag |
one soft-deletable flag row per offer |
p2p_reviews |
p2pReview |
three 0-100 scores — communication, speed, trust — plus feedback |
p2p_commissions |
p2pCommission |
escrow fees actually collected, per trade |
p2p_activity_logs |
p2pActivityLog |
the P2P event stream |
p2p_admin_activity |
p2pAdminActivity |
admin actions, written inside the transaction they describe |
All ten are paranoid — deletes are soft.
It stores communicationRating, speedRating and trustRating, each 0-100. A
1-5 star submission is spread across all three. Anything averaging a rating
column will throw.
Enums
p2pOffer.type BUY | SELL
p2pOffer.walletType FIAT | SPOT | ECO
p2pOffer.status DRAFT | PENDING_APPROVAL | ACTIVE | PAUSED
COMPLETED | CANCELLED | REJECTED | EXPIRED
p2pTrade.status PENDING | PAYMENT_SENT | COMPLETED
CANCELLED | DISPUTED | EXPIRED
p2pTrade.escrowStatus NONE | HELD | RELEASED | REFUNDED
p2pDispute.status PENDING | IN_PROGRESS | RESOLVED
p2pDispute.priority HIGH | MEDIUM | LOWDispute reasons: PAYMENT_NOT_RECEIVED, PAYMENT_INCORRECT_AMOUNT,
CRYPTO_NOT_RELEASED, SELLER_UNRESPONSIVE, BUYER_UNRESPONSIVE,
FRAUDULENT_ACTIVITY, TERMS_VIOLATION, OTHER.
Resolution outcomes: BUYER_WINS, SELLER_WINS, SPLIT, CANCELLED.
Wallet operation types
Every P2P money movement is booked under one of these, which is what makes a reconciliation query possible:
| Operation type | Raised by |
|---|---|
P2P_OFFER_LOCK |
a SELL offer taking or topping up its collateral |
P2P_OFFER_DELETE |
an offer releasing its attributed escrow (delete, pause, reject, disable, expire) |
P2P_TRADE_LOCK |
a taker's funds held when they take a BUY offer |
P2P_TRADE_RELEASE |
escrow consumed from the seller's hold at settlement |
P2P_TRADE_CANCEL |
the seller's share returned to spendable balance |
P2P_TRADE_RECEIVE |
the buyer's net credit |
Collected fees are booked as platform revenue under the P2P_TRADE type, with
reference p2p_fee_<tradeId>.
Escrow idempotency keys
All settlement doors share one namespace, which is what stops a trade being paid out twice by two different doors:
p2p_settle_<tradeId>_execute consume from the seller's hold
p2p_settle_<tradeId>_seller_credit seller's share back
p2p_settle_<tradeId>_buyer_credit buyer's net credit
p2p_settle_<tradeId>_eco_chain ECO per-chain ledger sync
p2p_trade_lock_<tradeId> BUY-offer taker's hold
p2p_offer_hold_<offerId>_<reference> offer collateral
p2p_offer_release_<offerId>_<ref> offer collateral releasedOffer-level keys carry a per-event reference because the wallet service
throws on a reused release key rather than no-opping — a fixed key would let
an offer be released exactly once in its lifetime and make the second
pause-or-delete fail with a 500.
Cron jobs
Registered under the p2p category. Both require the cron process to be
running.
| Job | Period | Does |
|---|---|---|
p2pTradeTimeout |
1 minute | expires overdue PENDING trades and returns their escrow to the parent offer; auto-disputes PAYMENT_SENT trades older than 24 hours; expires ACTIVE offers untouched for 30 days with zero remaining total |
updateP2PReputationScores |
1 hour | recomputes completion rate and review averages for users with P2P activity in the last 30 days, and records 10/50/100-trade milestones once each |
Each scan in the timeout job is capped at 500 rows per tick.
A daily "Archive Old P2P Trades" job used to be registered against a function
that only logged and returned, so the cron screen reported retention running
while nothing was archived. It has been deregistered rather than left as a
convincing no-op: P2P has no archive table and no archivedAt column, and
deleting completed trades is not an option because they are financial records.
Rate limiters
| Key | Limit | Window |
|---|---|---|
p2pOfferCreate |
5 | 1 hour |
p2pTradeInitiate |
20 | 1 hour |
p2pTradeAction |
50 | 1 hour |
p2pMessage |
100 | 1 hour |
p2pPaymentMethodCreate |
15 | 1 hour |
p2pDisputeCreate |
3 | 24 hours |
p2pSearch |
120 | 1 minute |
p2pAdminDispute |
100 | 1 hour |
p2pAdminTrade |
100 | 1 hour |
p2pAdminOffer |
50 | 1 hour |
KYC feature keys
| Key | Gates |
|---|---|
make_p2p_offer |
creating and editing an offer |
buy_p2p_offer |
opening a trade, either side |
Both fail open when platform KYC is not enabled at all.
Distributed locks
| Key | Held for | Taken by |
|---|---|---|
p2p:initiate:<offerId>:lock |
30 s | trade initiation |
p2p:trade:<tradeId>:action_lock |
30 s | confirm, release, cancel |
p2p:release:<tradeId>:<userId> |
1 hour | release result cache |
If Redis is unavailable these endpoints answer 503 rather than proceeding without a lock.