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.

2 min readUpdated 3 August 2026reference, api, permissions, schema

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.

The live board for one side of one market, with a same-side price reference and counterparty trust facts. A signed-in caller's own offers are included, flagged isOwn and ranked; includeOwn=0 drops them. Filters: side, currency, fiat, amount, paymentMethods, country, sort, page, limit, includeOwn.
A short list of suggested counterparties, each labelled with why it was picked and the trade-off it carries.
The market as a list of people rather than rows — one entry per counterparty with their trust record and both prices they quote.
Aggregated market statistics from P2P trades.
Top cryptocurrencies by P2P trade volume.
Highlighted market data, for example top active offers.
Best-effort country and fiat currency for the caller — profile, then CDN geo header, then a cached IP lookup. Returns nulls rather than guessing.
Currencies that currently have at least one active, publicly visible offer, most-offered first.
Landing page data: stats, top cryptos, featured offers, top traders and payment methods.
Distinct countries extracted from user profile locations.
A single offer with computed seller metrics and ratings.
Offers ordered by a popularity score from completed trades and review averages.
Paginated, filterable offer list.

Offers

Create an offer. Send status=DRAFT to store an unpublished draft that takes no escrow and skips publish validation. Rate limit 5/hour. KYC feature make_p2p_offer.
Update an offer. Refused while any trade against it is PENDING, PAYMENT_SENT or DISPUTED. Recomputes SELL escrow. KYC feature make_p2p_offer.
Delete an offer. Refused with in-flight trades; releases the offer's attributed escrow.
The authenticated user's own offers, including DRAFT, PENDING_APPROVAL, ACTIVE and PAUSED.
Pre-publish forecast: where a price lands on the board, how many traders could pass the requirements, how long comparable offers waited. Every figure nullable.
Open a trade against an offer. Takes a Redis lock and a SERIALIZABLE transaction. Rate limit 20/hour. KYC feature buy_p2p_offer.

Trades

Aggregated trade dashboard data for the authenticated user.
Paginated, filterable trade history. Rate limit 120/minute.
A single trade, buyer or seller only.
Event-driven trade subscription — initial data on subscribe, then pushes on status changes, messages and disputes. No polling.
Buyer declares payment sent. PENDING only, inside the payment window. Stamps paymentConfirmedAt.
Seller releases escrow to the buyer. PAYMENT_SENT only. Idempotent by Redis cache and by escrowStatus.
Cancel a trade. Buyer only while PENDING; refused after payment is confirmed and while disputed. Reason of 10+ characters required.
Open a dispute. PAYMENT_SENT only. Reason enum plus a 20-1000 character description and up to 5 evidence items. Rate limit 3/24h.
Withdraw a dispute you opened. Only the filer, only while the dispute is still PENDING. Returns the trade to the status it held before, marks the dispute RESOLVED with outcome WITHDRAWN, and moves no funds.
Review the counterparty after completion — a 1-5 star rating, or explicit communication/speed/trust scores 0-100. Rate limit 50/hour.
Trade chat messages, read from the trade timeline.
Send a chat message, max 1000 characters. Rate limit 100/hour.
Upload an image attachment, max 5 MB.
Stream a chat attachment. Readable only by the buyer and the seller of that trade.

Payment methods, wallets and dashboard

The caller's usable payment methods — global ones plus their own.
Create a personal payment method. Max 20 per user, unique name per user. Rate limit 15/hour.
Update a personal payment method.
Delete a personal payment method. Refused while used by an ACTIVE/PENDING_APPROVAL/PAUSED offer or a PENDING/PAYMENT_SENT/DISPUTED trade.
Spendable balances across FIAT, SPOT and ECO — what each wallet can commit to a NEW trade. Used to cap the amount field before a sell-side trade.
Combined dashboard payload: notifications, portfolio, stats, activity and transactions.
Recent trading activity for the authenticated user.
Portfolio summary for the authenticated user.
Trade counts and stats for the authenticated user.
Recent trade transactions for the authenticated user.
Find matching offers from guided criteria.

Admin routes

P2P admin dashboard totals, growth, distributions and analytics.
Platform activity aggregated by month for the current year.
Recent activity entries for the dashboard panel.
Full activity log with pagination and filtering.

Offers

List all offers.
One offer with owner, payment methods, statistics and pricing.
Update an offer with admin privileges, including status transitions and SELL escrow management. Rate limit 50/hour.
Approve a pending offer and set it ACTIVE. Validates amount and payment methods, notifies the owner.
Reject an offer. Releases locked funds and notifies with the reason. Terminal.
Pause an ACTIVE offer. Releases locked funds back to the owner.
Activate a paused, disabled, rejected or cancelled offer. Tops SELL escrow up to the advertised total.
Disable an offer, setting it CANCELLED and releasing locked funds.
Flag an offer for review. Writes a flag row and notifies the owner; does not change status.
Append a timestamped internal note to the offer. Never shown to users.

Trades

List all trades with pagination and filtering.
Full trade detail including the timeline and payment snapshot.
Force-resolve a trade in DISPUTED, PAYMENT_SENT or PENDING. Outcomes BUYER_WINS, SELLER_WINS, SPLIT, CANCELLED. Rate limit 100/hour.
Cancel a trade with a reason, settling the escrow back and restoring offer capacity.
Add an internal note, or with isMessage=true post a user-visible admin message into the trade chat.
Run the trade timeout handler immediately — the same work the one-minute cron does.

Disputes

List all disputes.
One dispute with trade, parties, messages, evidence and admin notes.
Update a dispute: change status, post an admin message, or record a resolution that settles the escrow. Rate limit 100/hour.
Attach evidence images to a dispute, stamped with the admin and timestamp.
Add an internal admin note to the dispute activity log.

Payment methods

List every payment method, global and personal.
Create a global payment method available to all users. Requires name and icon.
Read one payment method.
Update any payment method and toggle its global status.
Soft-delete any payment method.

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_method

Setting 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 | LOW

Dispute 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 released

Offer-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.