Documentación para socios

Todo lo necesario para publicar sus anuncios de venta y de recompra en QuizzBuy y notificarnos los pedidos atribuidos a nuestro tráfico.

1. Obtener una clave API

Contacte con su interlocutor de QuizzBuy (o el formulario de contacto). Generamos para usted una clave con el formato zzb_live_… — solo se le comunica una vez, guárdela en un lugar seguro. Inclúyala en cada solicitud en la cabecera HTTP:

Authorization: Bearer zzb_live_VOTRE_CLE

2. Verificar su clave

curl https://quizzbuy.com/api/v1/me \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"

# Réponse
{
  "company": { "id": "…", "name": "Votre société", "slug": "votre-societe" },
  "commission_rate": 0.05,
  "click_param": "zzb_click"
}

3. Publicar sus anuncios

Un anuncio es una oferta de recompra (BUYBACK — usted recupera un dispositivo a un precio determinado), o bien una oferta de venta (SALE — un producto reacondicionado que usted vende). El envío es un upsert: reenviar el mismo externalId actualiza el anuncio.

curl -X POST https://quizzbuy.com/api/v1/listings \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "ref-interne-123",
    "type": "BUYBACK",
    "title": "Reprise iPhone 15 Pro 256 Go",
    "brand": "Apple",
    "model": "iPhone 15 Pro",
    "category": "SMARTPHONE",
    "storage": "256 GB",
    "condition": "GOOD",
    "priceCents": 52000,
    "currency": "EUR",
    "url": "https://votre-site.fr/reprise/iphone-15-pro",
    "country": "FR"
  }'
  • category: SMARTPHONE, LAPTOP, SMARTWATCH, AUDIO o GAMING.
  • condition: LIKE_NEW, EXCELLENT, GOOD, FAIR o BROKEN.
  • priceCents: precio en céntimos (52000 = 520 €).
  • Cada anuncio nuevo (o modificación) pasa a moderación antes de su publicación.

Actualizar, listar o desactivar:

# Lister vos annonces
curl "https://quizzbuy.com/api/v1/listings?type=BUYBACK&status=APPROVED" \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"

# Mise à jour partielle
curl -X PATCH https://quizzbuy.com/api/v1/listings/ID_ANNONCE \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{ "priceCents": 49000 }'

# Désactiver (soft delete)
curl -X DELETE https://quizzbuy.com/api/v1/listings/ID_ANNONCE \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"

4. Seguimiento de clics

Cuando un visitante de QuizzBuy hace clic hacia su sitio, la URL de destino contiene un identificador de clic:

https://votre-site.fr/vendre?zzb_click=6f1e0c9a-3b2d-4e8f-9a10-abcdef123456

Guarde este valor (cookie o sesión en su sitio, duración recomendada: 45 días). Es el que permitirá atribuir el pedido. El nombre del parámetro (zzb_click por defecto) es configurable a petición.

5. Notificar un pedido (postback S2S)

En cuanto un cliente realiza un pedido (compra o solicitud de recompra validada) y usted dispone de un zzb_click, llame a nuestro postback desde su servidor:

curl -X POST https://quizzbuy.com/api/v1/postback \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{
    "click_id": "6f1e0c9a-3b2d-4e8f-9a10-abcdef123456",
    "type": "BUYBACK",
    "amount_cents": 52000,
    "currency": "EUR",
    "order_ref": "CMD-2026-000123"
  }'

# Réponse 201
{ "conversion_id": "…", "status": "TO_INVOICE", "commission_cents": 2600 }
  • Ventana de atribución: 45 días tras el clic; pasado ese plazo, respuesta 422 attribution_window_expired.
  • order_ref debe ser único: un duplicado devuelve 409 duplicate_order_ref (idempotencia — puede reintentar sin riesgo).
  • type: SALE para una compra, BUYBACK para una recompra.
  • currency: solo EUR — cualquier otra divisa es rechazada (422 unsupported_currency).

6. Probar en sandbox

Añada "test": true al postback: la solicitud se valida íntegramente (clave, click_id, ventana de 45 días) pero no se registra ni se factura ninguna conversión.

{ "click_id": "…", "type": "SALE", "amount_cents": 10000, "order_ref": "TEST-1", "test": true }
# → 200 { "test": true, "valid": true, "commission_cents": 500 }

7. Códigos de error

CódigoErrorExplicación
400invalid_inputCuerpo de solicitud inválido (detalles en la respuesta).
401unauthorizedClave API ausente, revocada o inválida.
404click_not_found / not_foundclick_id o anuncio desconocido (o no le pertenece).
409duplicate_order_refPedido ya notificado.
422attribution_window_expiredClic de más de 45 días.
429rate_limitedDemasiadas solicitudes — reinténtelo dentro de un minuto.

8. Facturación

Cada conversión atribuida genera una comisión (tasa contractual, visible mediante /api/v1/me). QuizzBuy le envía una factura recapitulativa periódica de las conversiones validadas. Para cualquier duda: contáctenos.

9. Marketplace — vender en QuizzBuy

Los anuncios SALE con un quantity > 0 se venden directamente en QuizzBuy (pago del cliente en nuestra plataforma, abono del neto de comisión). Campos adicionales: quantity (stock), color, grade (A/B/C), batteryHealth (%), warrantyMonths. El anuncio se vincula automáticamente a la ficha de producto correspondiente (marca + modelo + capacidad + color).

# Mettre à jour le stock (sans re-modération)
curl -X PATCH https://quizzbuy.com/api/v1/listings/ID_ANNONCE/stock \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{ "quantity": 12 }'

# Ajouter une photo (multipart, max 5 × 5 Mo, jpeg/png/webp)
curl -X POST https://quizzbuy.com/api/v1/listings/ID_ANNONCE/images \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -F "file=@photo.jpg"

10. Webhooks — ser avisado de las ventas

Registre un endpoint HTTPS; le notificamos en cada etapa de un pedido que contenga sus productos (order.created, order.paid, order.cancelled). El secreto solo se devuelve en la creación — guárdelo.

curl -X POST https://quizzbuy.com/api/v1/webhooks \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://votre-site.fr/webhooks/quizzbuy", "events": ["order.paid"] }'

# Réponse (secret affiché une seule fois)
{ "webhook": { "id": "…", "secret": "whsec_…", "events": ["order.paid"] } }

# Tester
curl -X POST https://quizzbuy.com/api/v1/webhooks/ID_WEBHOOK/test \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"

Cada entrega está firmada. Verifique la cabecera X-ZZbuy-Signature (t=timestamp,v1=hex):

// Node.js
const [t, v1] = signature.split(",").map((p) => p.split("=")[1]);
const expected = crypto
  .createHmac("sha256", WEBHOOK_SECRET)
  .update(`${t}.${rawBody}`)
  .digest("hex");
const valid =
  crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1)) &&
  Math.abs(Date.now() / 1000 - Number(t)) < 300; // anti-replay 5 min

En caso de fallo (≠ 2xx), reintentamos con backoff: 1 min, 5 min, 30 min, 2 h, 12 h. También recibe un correo de notificación de venta.

11. Gestionar sus pedidos

Liste sus líneas de pedido, confirme la recepción y luego envíe con un número de seguimiento (el cliente es notificado automáticamente). La dirección de entrega solo es visible tras el pago.

# Lister les commandes à traiter
curl "https://quizzbuy.com/api/v1/orders?status=PENDING" \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"

# Accuser réception
curl -X POST https://quizzbuy.com/api/v1/orders/ID_LIGNE/ack \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"

# Expédier
curl -X POST https://quizzbuy.com/api/v1/orders/ID_LIGNE/ship \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{ "carrier": "Colissimo", "trackingNumber": "6A123456789FR" }'

12. Recompra gestionada (trade-in)

La recompra gestionada va más allá de la simple recompra por redirección: el particular envía su dispositivo desde QuizzBuy, y usted gestiona todo el expediente a través de la API (aceptación, recepción, contraoferta, pago). El precio mostrado al cliente queda bloqueado en el momento del envío según su tabla de recompra (véase §14); solo puede revisarlo a la baja tras la recepción mediante una contraoferta motivada.

Todos los endpoints se autentican con su clave API (Authorization: Bearer zzb_live_…) y solo devuelven sus expedientes.

Ciclo de vida

SUBMITTEDACCEPTEDSHIPPEDRECEIVEDPAID. Dos ramificaciones: REJECTED (rechazo antes del envío) y COUNTER_OFFER (contraoferta tras la inspección, que el particular acepta — y pasa a PAID — o rechaza — devolución del dispositivo, CANCELLED).

EstadoSignificado
SUBMITTEDSolicitud creada por el particular, pendiente de su decisión.
ACCEPTEDUsted ha confirmado la recompra; el cliente debe enviar el dispositivo.
SHIPPEDEl particular ha indicado su número de seguimiento.
RECEIVEDUsted ha recibido el dispositivo; inspección en curso.
COUNTER_OFFERTras la inspección, usted propone un importe revisado (inferior).
PAIDPago realizado al particular — expediente cerrado.
REJECTEDSolicitud rechazada antes del envío (no elegible, fraude…).
CANCELLEDCancelada (rechazo de una contraoferta, devolución del dispositivo).

Listar y consultar

# Lister vos reprises (plus récentes d'abord)
curl "https://quizzbuy.com/api/v1/trade-ins?status=SUBMITTED&limit=50&offset=0" \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"

# Réponse
{
  "total": 3,
  "trade_ins": [
    {
      "trade_in_id": "…",
      "reference": "TI-2026-000042",
      "status": "SUBMITTED",
      "device": {
        "brand": "Apple", "category": "SMARTPHONE", "model": "iPhone 15 Pro",
        "storage": "256 GB", "condition": "GOOD",
        "functional_status": "FULLY_WORKING", "battery_health": 92, "imei": "…"
      },
      "offer_cents": 52000,
      "currency": "EUR",
      "price_locked_until": "2026-08-05T12:00:00.000Z",
      "country": "FR",
      "customer": { "email": "…", "first_name": "…", "last_name": "…", "locale": "fr" },
      "comment": null,
      "counter_offer_cents": null, "counter_reason": null,
      "tracking_carrier": null, "tracking_number": null,
      "shipping_label_url": null, "payout_ref": null,
      "accepted_at": null, "shipped_at": null, "received_at": null,
      "paid_at": null, "cancelled_at": null,
      "created_at": "2026-07-21T12:00:00.000Z"
    }
  ]
}

# Détail d'une reprise
curl https://quizzbuy.com/api/v1/trade-ins/ID_REPRISE \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"
# → { "trade_in": { … } }
  • status (filtro opcional): un valor de la tabla anterior — de lo contrario 400 invalid_status.
  • limit: máx. 200 (por defecto 50); offset para la paginación.
  • El customer (contacto del particular) solo es visible en esta API de socios, nunca en los webhooks.

Transiciones

Cada acción es un POST y devuelve { "ok": true, "trade_in": { … } }. Una transición desde un estado incompatible devuelve 409 invalid_status (con el current real). El particular es notificado por correo en cada etapa.

# 1. Accepter (depuis SUBMITTED) — shippingLabelUrl facultatif (étiquette prépayée)
curl -X POST https://quizzbuy.com/api/v1/trade-ins/ID_REPRISE/accept \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{ "shippingLabelUrl": "https://votre-site.fr/labels/ti-42.pdf" }'

# 2. Réceptionner l'appareil (depuis SHIPPED ou ACCEPTED)
curl -X POST https://quizzbuy.com/api/v1/trade-ins/ID_REPRISE/receive \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"

# 3a. Payer le montant garanti (depuis RECEIVED) — clôt la reprise
curl -X POST https://quizzbuy.com/api/v1/trade-ins/ID_REPRISE/pay \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{ "payoutRef": "VIR-2026-000123" }'

# 3b. …ou contre-offrer après inspection (depuis RECEIVED) — montant < offre garantie
curl -X POST https://quizzbuy.com/api/v1/trade-ins/ID_REPRISE/counter \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{ "amountCents": 42000, "reason": "Rayures écran non déclarées" }'

# Refuser avant envoi (depuis SUBMITTED uniquement)
curl -X POST https://quizzbuy.com/api/v1/trade-ins/ID_REPRISE/reject \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE"
  • accept: solo desde SUBMITTED. shippingLabelUrl opcional (URL, ≤ 500 car.).
  • receive: desde SHIPPED o ACCEPTED (depósito/envío no declarado por el cliente).
  • counter: desde RECEIVED. amountCents debe ser estrictamente inferior a offer_cents, de lo contrario 400 counter_not_lower; reason obligatorio (1–500 car.). Es luego el particular quien acepta o rechaza desde su página de seguimiento.
  • pay: desde RECEIVED. payoutRef obligatorio (referencia de transferencia, 1–120 car.).
  • reject: solo desde SUBMITTED — no se requiere ningún cuerpo.

13. Webhooks de recompra (trade_in.*)

Los mismos webhooks (§10, firma X-ZZbuy-Signature idéntica) cubren la recompra gestionada. Suscríbase a todos o parte de los eventos trade_in.* al crear un endpoint:

curl -X POST https://quizzbuy.com/api/v1/webhooks \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://votre-site.fr/webhooks/quizzbuy",
    "events": ["trade_in.created", "trade_in.shipped", "trade_in.counter_accepted"]
  }'
EventoDesencadenante
trade_in.createdNueva solicitud enviada por un particular (estado SUBMITTED).
trade_in.shippedEl particular ha indicado su seguimiento de envío.
trade_in.counter_acceptedEl particular ha aceptado su contraoferta.
trade_in.counter_declinedEl particular ha rechazado su contraoferta (devolución del dispositivo).
trade_in.cancelledRecompra cancelada.

Cuerpo de la entrega (el contacto del particular no figura en él — obténgalo mediante GET /api/v1/trade-ins/:id):

{
  "trade_in_id": "…",
  "reference": "TI-2026-000042",
  "status": "SHIPPED",
  "device": {
    "brand": "Apple", "category": "SMARTPHONE", "model": "iPhone 15 Pro",
    "storage": "256 GB", "condition": "GOOD",
    "functional_status": "FULLY_WORKING", "battery_health": 92, "imei": "…"
  },
  "offer_cents": 52000,
  "currency": "EUR",
  "price_locked_until": "2026-08-05T12:00:00.000Z",
  "country": "FR"
}

14. Enviar su tabla de recompra

Alternativa «push» al flujo CSV extraído por nuestra parte: envíe directamente su tabla de precios de recompra. Cada línea contiene los 4 importes según el estado del dispositivo. Los precios alimentan la misma tabla que la sincronización automática — por lo tanto aparecen inmediatamente en el comparador y sirven como precio bloqueado para la recompra gestionada (§12). La operación es un upsert por (marca, categoría, modelo, capacidad).

curl -X PUT https://quizzbuy.com/api/v1/buyback/prices \
  -H "Authorization: Bearer zzb_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{
    "prices": [
      {
        "brand": "Apple",
        "category": "SMARTPHONE",
        "model": "iPhone 15 Pro",
        "storage": "256 GB",
        "priceNewCents": 60000,
        "priceGoodCents": 52000,
        "priceFairCents": 40000,
        "priceBrokenCents": 15000,
        "currency": "EUR",
        "url": "https://votre-site.fr/reprise/iphone-15-pro"
      }
    ]
  }'

# Réponse
{ "ok": true, "upserted": 1 }
  • prices: de 1 a 2000 líneas por solicitud.
  • category: SMARTPHONE, TABLET, LAPTOP, SMARTWATCH, AUDIO o GAMING.
  • priceNewCents (nuevo), priceGoodCents (bueno), priceFairCents (marcado), priceBrokenCents (roto) — en céntimos, ≥ 0.
  • storage y url opcionales; la capacidad se normaliza automáticamente (ej. «256go» → «256 GB»).