QvaPay API
P2P

Listar Ofertas P2P

Obtener el listado paginado de ofertas P2P disponibles con filtros avanzados.

GET /p2p

Retorna el listado de ofertas P2P disponibles, con soporte para filtros por tipo, moneda, monto, ratio, estado y paginación.

Autenticación

Bearer Token (Authorization: Bearer <token>) o Credenciales de App (app-id + app-secret en headers).

Cuando se usa Credenciales de App, la consulta se realiza en nombre del usuario propietario de la app. Dicho usuario debe cumplir los requisitos de acceso a P2P (p2p_enabled, KYC verificado, Telegram vinculado y teléfono verificado).

Request

Con Bearer Token:

curl -X GET "https://api.qvapay.com/p2p?take=20&page=1&type=buy&coin=BANK_CUP" \
  -H "Authorization: Bearer <token>"

Con Credenciales de App:

curl -X GET "https://api.qvapay.com/p2p?take=20&page=1&type=buy&coin=BANK_CUP" \
  -H "app-id: {tu-app-uuid}" \
  -H "app-secret: {tu-app-secret}"

Parámetros (query string)

ParámetroTipoRequeridoDescripción
pagenumberNoPágina a consultar (por defecto 1)
takenumberNoResultados por página (por defecto 20, máximo 100)
typestringNoFiltrar por tipo: buy o sell (alias: offer_type)
coinstringNoFiltrar por tick de la moneda (ej: BANK_CUP)
orderBystringNoCampo de ordenamiento: updated_at (por defecto), created_at, amount, receive, ratio, best_rate, rating, trades
orderTypestringNoOrden: asc o desc (por defecto desc; alias: order). Con orderBy=best_rate, desc es "mejor tasa primero"
mybooleanNotrue o 1 muestra solo las ofertas del usuario autenticado (como dueño, como peer, o las ofertas por rango de las que tomó una parte)
peerstringNoFiltrar por usuario: acepta UUID o username (con o sin @)
sortByStatusbooleanNoSolo con my=true: agrupa por estado (revisión → procesando → pagado → abierta → completada). Por defecto true
minnumberNoMonto mínimo (alias: min_price)
maxnumberNoMonto máximo (alias: max_price)
ratio_minnumberNoRatio mínimo (receive / amount) (alias: min_ratio)
ratio_maxnumberNoRatio máximo (receive / amount) (alias: max_ratio)
offer_kindstringNoFiltrar por tipo de oferta: fixed (se toma entera) o flexible (por rango, se toma por partes). Sin filtro llegan las dos (alias: kind)
flexible_firstbooleanNotrue o 1 pone las ofertas por rango antes que las fijas, manteniendo el orden pedido dentro de cada grupo. Solo en el listado del mercado y sin offer_kind. Por defecto false; la web de QvaPay lo usa siempre
only_vipbooleanNotrue o 1 muestra solo ofertas marcadas como exclusivas para usuarios VIP
vip_makerbooleanNotrue o 1 muestra solo ofertas publicadas por cuentas VIP
searchstringNoBúsqueda general (detalles, mensaje, uuid, montos). Solo funciona junto a my=true
statusstringNoFiltrar por estado: open, revision, processing, paid, completed, cancelled. Solo aplica junto a my=true (el listado público siempre es open)

Los ordenamientos ratio (tasa receive / amount), best_rate, rating (valoración promedio del ofertante) y trades (operaciones P2P completadas del ofertante) son rankings globales sobre el listado público; en my=true caen a updated_at (salvo ratio, que sí se aplica sobre tus ofertas).

ratio vs best_rate

ratio es la tasa cruda receive / amount (moneda por USD) y no es lo mismo que "mejor tasa": su dirección se invierte según el lado del mercado, porque el ratio se mide siempre desde la oferta, no desde quien la toma.

Tipo de ofertaQuien la toma…El ratio es…Mejor para quien la toma
sellcompra QUSD pagando la monedalo que paga por dólarratio más bajo
buyvende QUSD y recibe la monedalo que recibe por dólarratio más alto

Por eso orderBy=ratio&orderType=desc sobre un listado type=sell devuelve las ofertas más caras primero. Usa orderBy=best_rate, que normaliza el signo según el tipo: orderType=desc es siempre "mejor primero" y asc "peor primero", en ambos lados.

best_rate requiere type y coin (responde 400 si faltan): sin tipo el signo no está definido, y los ratios de monedas distintas no son comparables entre sí (1000 CUP/USD vs 1.2 MLC/USD ordenaría por moneda, no por tasa). Tampoco aplica con my=true, donde la perspectiva es la del dueño de la oferta.

# Mejores ofertas para comprar CUP por transferencia bancaria
curl -X GET "https://api.qvapay.com/p2p?type=sell&coin=BANK_CUP&orderBy=best_rate&orderType=desc" \
  -H "Authorization: Bearer {token}"

Response

Paginación estilo Laravel. amount, receive y los demás campos decimales se serializan como strings.

{
  "current_page": 1,
  "data": [
    {
      "uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "type": "buy",
      "coin": "BANK_CUP",
      "amount": "50",
      "receive": "12500",
      "payment_type_id": 1,
      "message": "Pago rápido",
      "private": false,
      "only_vip": false,
      "status": "open",
      "created_at": "2024-06-20T14:30:00.000Z",
      "updated_at": "2024-06-20T14:30:00.000Z",
      "offer_kind": "fixed",
      "available_amount": "50",
      "reserved_amount": "0",
      "order_min": null,
      "order_max": null,
      "User": {
        "uuid": "550e8400-e29b-41d4-a716-446655440000",
        "username": "johndoe",
        "name": "John",
        "image": "https://example.com/avatar.png",
        "kyc": true,
        "vip": false,
        "golden_check": false,
        "phone_verified": true,
        "telegram_verified": true,
        "rating_avg": 4.85,
        "rating_count": 12,
        "_count": {
          "P2P": 34,
          "P2P_Peer": 18
        }
      },
      "Coin": {
        "name": "Transferencia CUP",
        "tick": "BANK_CUP",
        "logo": "https://example.com/cup.png",
        "price": "1"
      }
    }
  ],
  "per_page": 20,
  "total": 143
}

Notas:

  • requirements trae los requisitos del creador para aplicar (min_completed, min_rating, min_account_days, min_trustscore), o null. Para saber si tu cuenta los cumple, pide el detalle (requirements_met).
  • offer_kind indica el tipo de cada oferta. En las flexible lo que se puede tomar es available_amount (no amount, que es el total original), en órdenes de entre order_min y order_max. Ver Ofertas por rango.
  • Las ofertas por rango aparecen para todas las cuentas, identificadas por offer_kind.
  • _count.P2P y _count.P2P_Peer son las operaciones P2P completadas del usuario como dueño y como peer, respectivamente.
  • En ofertas con peer asignado (visible con my=true) se incluye un objeto Peer con la misma estructura que User.
  • Con my=true, una oferta por rango de la que tomaste una parte llega con my_order (uuid, amount, receive, status, created_at): tu orden activa o, si no hay, la más reciente. En esas filas el filtro status se evalúa sobre tu orden (my_order.status), no sobre la oferta, cuyo status sigue open mientras otros toman. Las órdenes canceladas no salen salvo con status=cancelled.
  • Con my=true sin status explícito, la respuesta incluye "sorted_by_status": true y las ofertas llegan agrupadas por estado.

Límites y frescura

25 consultas por minuto por cuenta. Con credenciales de app el cupo es de la cuenta dueña, así que toda tu integración comparte un único bucket.

El listado del mercado —el que no lleva my ni peer— se cachea unos segundos en nuestros servidores. Es la misma respuesta para todo el que pide los mismos filtros, así que pedirla más rápido que la caché no devuelve datos más frescos: solo gasta cupo. Una consulta cada 2 o 3 segundos aprovecha el límite sin desperdiciarlo.

La cabecera X-Cache te dice si esa respuesta salió de la caché (HIT) o de la base de datos (MISS).

Si recibes un 429, el mensaje trae el límite vigente. Reintenta con backoff exponencial; no sirve de nada insistir antes.

Si tu integración necesita enterarse de cambios al instante, hacer polling no es el camino: ya existe el feed del mercado, que te avisa cuando de verdad pasa algo. Puedes recibirlo por webhook firmado o por stream, y convierte miles de consultas diarias en un aviso por evento.

Errores

CódigoDescripción
400Parámetro inválido (page, take, orderBy, orderType, type, status o filtros numéricos), orderBy=best_rate sin type/coin, o el usuario no cumple los requisitos de acceso a P2P
401Token de autenticación inválido o ausente
429Demasiadas solicitudes (límite: 30 cada 60 segundos por usuario)