Ir al contenido

Endpoints API

Backend: tcgcards-api corriendo en Cloud Run. Todos los endpoints REST. Prefix: /api/v1.

URL prod: https://tcgcards-api-1033181994095.us-central1.run.app

Hay 4 tipos de auth distintos según el endpoint:

TipoCómo se envíaQuién la usa
noneEndpoints públicos (catálogo, feeds)
user JWTAuthorization: Bearer <jwt>Login con Google → JWT propio firmado HS256
adminuser JWT + user.isAdmin === trueSolo admins del marketplace
admin tokenX-Admin-Token: <token>Endpoints de catálogo sync — usa ADMIN_TOKEN env var
internal authX-Internal-Auth: <secret>Server-to-server (Next.js → API). Usa INTERNAL_AUTH_SECRET
internal cronX-Internal-Cron-Secret: <secret>Solo Cloud Scheduler. Usa INTERNAL_CRON_SECRET
MétodoPathAuthDescripción
GET/api/v1/healthnoneHealthcheck (devuelve status de conexión Mongo)
GET/api/v1/tcgsnoneLista TCGs habilitados
GET/api/v1/tcgs/:slugnoneTCG específico
GET/api/v1/tcgs/:slug/raritiesnoneRarezas disponibles
GET/api/v1/setsnoneLista paginada ?tcg=
GET/api/v1/sets/:idnoneSet específico
GET/api/v1/cardsnoneBusca/filtra cards con facetas
GET/api/v1/cards/suggestnoneAutocomplete
GET/api/v1/cards/countsnoneConteo de matches por TCG para ?q= (1 consulta)
GET/api/v1/cards/:idnoneCard específica
GET/api/v1/cards/:id/listingsnoneListings activos de esa card
GET/api/v1/cards/:id/recent-salesnoneÚltimas 5 ventas
GET/api/v1/sitemap/cardsnoneCards para sitemap XML
GET/api/v1/sitemap/usersnoneUsernames públicos para sitemap

GET /api/v1/cards (y los feeds de listings tipo card, ej. /api/v1/listings/cards) aceptan filtros de atributo declarativos según el TCG, además de los conocidos (tcg, set, rarity, q, inStock, page, limit). Cada TCG define sus propios filtros, label y path en el registro src/services/tcgFilters.ts (ej. Pokémon: energyType, stage; One Piece: color, cardType).

  • Query: cada filtro es su propio parámetro con valores separados por coma → OR dentro del mismo filtro, AND entre filtros distintos. Ej: ?tcg=pokemon&energyType=Fire,Water&stage=Basic.
  • Solo keys registradas para ese TCG se aplican (las demás se ignoran). El backend mapea cada key a su path en attributes.raw.* y arma el $match ($in para arrays/escalares; los multi-valor con splitOn usan regex).
  • Respuesta facets.attributes: junto a sets y rarities, /cards devuelve facets.attributes: [{ key, label, order, values: [{ value, count }] }]. Sigue Modelo B (auto-exclusión): el conteo de cada filtro ignora su propia selección pero respeta los otros, para poder sumar valores sin que el conteo caiga a 0.
  • Consistencia con búsqueda: cuando hay q, las facetas se calculan vía Atlas Search ($search como stage líder, mismo índice card-search que los resultados), con fallback a regex solo si Atlas no está disponible (tests/CI). Así el conteo de una faceta coincide con lo que devuelve al hacer click.
  • q matchea nombre O número (jun 2026): además del nombre, q busca por número de carta (ej. 97, 97/101, OP09-095) vía el campo numberSearch (ver DB). Una query con dígitos se rutea al path indexado (regex de nombre + prefijo de número), NO a Atlas; y el conteo por TCG (/cards/counts, tabs) usa ese mismo $or (countByTcgNameOrNumber) → el número del tab coincide exacto con los resultados (sin double-count). Aplica también a searchSellerActiveListings (mis publicaciones + perfiles públicos) y al feed /listings/cards. El escáner NO usa este path (identifica por número aparte). Backfill: scripts/backfill-number-search.ts.
  • inStock es dinámico: con inStock=true, TODAS las facetas (sets, rarezas, atributos) cuentan solo cartas con listings activos. No es una faceta sino un filtro binario, así que no se auto-excluye.

Las facetas se memoizan en proceso (TTL corto, sin Redis; inStock es parte de la clave) y se apoyan en ~19 índices parciales {tcg:1, <path>:1} sobre cards (ver base de datos).

GET /api/v1/cards/counts?q=<término> devuelve, en una sola consulta de agregación, cuántas cartas hacen match por cada TCG: { data: [{ tcg, count }] } (solo TCGs con al menos un match). Lo usa la búsqueda global del web (cuando no hay tcg en la URL) para elegir directo el TCG con más resultados y para mostrar el conteo junto a cada tab — antes lanzaba 9 búsquedas completas en paralelo.

  • Usa Atlas Search ($search sobre el índice card-search, mismas cláusulas de autocomplete que /cards) + $group por tcg, con fallback a $regex sobre baseName si Atlas no está disponible.
  • q vacío → []. Cualquier error degrada a regex y, en último caso, a []: nunca rompe la página.
  • Es solo lectura y no afecta la query de resultados de /cards (stock, orden, facetas).

Rendimiento del stock (2026-06-10): el stock de una carta NO está denormalizado; vive en listings. La búsqueda resuelve los cardIds con listings activos en UNA consulta a listings (lista chica) y la usa para el orden “con stock primero” ($in en memoria), el filtro inStock y las facetas; el $lookup de minPrice/listingsCount corre solo para la página visible (≤ 24 cartas), no para todo el catálogo. (Antes era un $lookup por carta matcheada: navegar Magic —111k cartas— tardaba ~14s.) Si algún día hay decenas de miles de cartas distintas publicadas a la vez, la mejora de fondo es denormalizar listingsCount en cards.

MétodoPathAuthDescripción
GET/api/v1/listingsnoneBúsqueda general (admin-grade)
GET/api/v1/listings/cardsnoneFeed público de listings tipo card. Filtros: q, tcg, rarity (rareza de la carta de catálogo; excluye publicaciones custom), condition, sort, offset, limit + filtros de atributo por TCG (tipo de carta, energía, etc.; mismas keys del registro tcgFilters.ts, ej. ?tcg=pokemon&cardType=Pokemon,Supporter). Los atributos solo aplican con tcg
GET/api/v1/listings/cards/facetsnoneFacetas tcgs, rarities y attributes. Acepta q/tcg/condition/rarity (+ atributos) con Modelo B (cada faceta aplica todos los filtros menos el propio). attributes se calcula solo con tcg (son por-TCG; [] sin tcg) y la faceta tcgs NO aplica atributos (sigue conmutable entre juegos). Conteos = cartas únicas (los grupos del feed, no listings) con orden estable (count desc, desempate alfabético); total es global
GET/api/v1/listings/bulknoneFeed público de challa
GET/api/v1/listings/bulk/facetsnoneFacetas challa
GET/api/v1/listings/sealednoneFeed público de sellado
GET/api/v1/listings/sealed/facetsnoneFacetas sellado
GET/api/v1/accessoriesnoneFeed de accesorios
GET/api/v1/accessories/facetsnoneFacetas accesorios
GET/api/v1/listings/decksnoneFeed público de mazos
GET/api/v1/listings/decks/facetsnoneFacetas decks (counts por TCG)
GET/api/v1/listings/customnoneCustom listings (legacy)
GET/api/v1/listings/:id/publicnoneListing público
GET/api/v1/listings/:idnoneListing detallado
POST/api/v1/listingsuser JWT + activeCrea listing
POST/api/v1/listings/batchuser JWT + activePublicación masiva (Publicador Pro): crea N publicaciones card en un request, resultado por carta (éxito parcial), idempotente por clientRef
PATCH/api/v1/listings/:iduser JWT + activeActualiza listing propio
DELETE/api/v1/listings/:iduser JWTElimina listing propio
GET/api/v1/users/me/listingsuser JWTMis listings con filtros
GET/api/v1/users/me/listings/facetsuser JWTFacetas de mis listings (incluye autoPriceEligibleCount = nº de cartas activas de TCGplayer, lo que actualiza “activar en todas”)
POST/api/v1/users/me/listings/bulkuser JWTAcción bulk: pause/activate/mark_sold/delete
POST/api/v1/listings/:id/auto-priceuser JWT + activeActiva/desactiva Precios dinámicos en una carta (body { enabled }; solo cartas de catálogo de TCGplayer elegibles). Rate-limited 60/min
POST/api/v1/me/listings/auto-price/apply-alluser JWT + activeActiva/desactiva Precios dinámicos en todas las cartas elegibles del vendedor (body { enabled }). Rate-limited 10/min
POST/api/v1/me/listings/auto-price/recalcuser JWT + activeRecalcula los precios ahora (todas las del vendedor, o solo una si se pasa listingId). Devuelve los precios aplicados + considered/noFreshPrice/belowMin. Rate-limited 10/min
GET/api/v1/listings/auto-price/last-refreshnoneTimestamp del último refresco de precios de TCGplayer
GET/api/v1/users/:username/listingsnoneListings públicos de un seller
GET/api/v1/users/:username/listings/facetsnoneFacetas del seller

GET /users/me/listings incluye card.marketPrices (precio de referencia de TCGplayer, USD) por publicación de carta — lo usa el front para mostrarle al vendedor la referencia junto a su precio. Los endpoints de cartas (/cards, /cards/:id, /cards/suggest) ya exponían marketPrices.

  • GET /api/v1/listings/decks — feed público de mazos. Query params:

    • q (string, opc): búsqueda case-insensitive en deckTitle.
    • tcg (string slug, opc): filtra por TCG.
    • sort (recent | price-asc | price-desc, default recent).
    • offset (int, default 0), limit (int, default 24, max 48).
    • Respuesta: { data, total, nextOffset }.
  • GET /api/v1/listings/decks/facets — counts por TCG sobre el universo de decks activos con stock. Respuesta: { total, tcgs: [{ slug, count }] }.

  • POST /api/v1/listings con kind: 'deck' — crear un mazo. Body:

    • deckTitle (5-80 chars), tcg (slug), deckCardCount (1-500), photos (1-5 URL Cloudinary), description (10-500 chars), price, quantity, location.
  • PATCH /api/v1/listings/:id — admite campos deckTitle y deckCardCount además de los comunes.

  • POST /api/v1/listings con kind: 'card' — acepta photos (0-2 URLs Cloudinary) opcionales: fotos propias del vendedor de su carta real (frente/dorso), además de los campos de carta. La imagen del catálogo no cambia; estas son adicionales. El PATCH también las admite (cap 2).

Feature opt-in por vendedor: una vez activado, sus cartas de catálogo de TCGplayer se repricen solas cada día. El precio se calcula así:

precio_CLP = round100(card.marketPrices.market[USD] × usdRate_del_vendedor)

  • usdRate es el tipo de cambio (CLP por USD) que define el vendedor en su configuración maestra.
  • Redondeo: half-up al múltiplo de $100 más cercano (aritmética entera).
  • Mínimo de publicación: $50. Una carta cuyo precio calculado quede por debajo no se actualiza (belowMin).

Qué cartas aplican. Solo cartas de catálogo de TCGplayer (card.source === "tcgplayer"). Nunca Mitos y Leyendas (source api.myl.cl), sellado, accesorios ni custom.

Gate de frescura (26h). Para repricar una carta, tanto el set (sets.pricesRefreshedAt) como la carta (cards.marketPrices.pricesUpdatedAt) deben tener precio fresco (≤ 26h). Si no, esa carta se salta (no_fresh_price).

Configuración maestra del vendedor. PATCH /users/me acepta autoPricing: { enabled, usdRate } (usdRate int 1..100000 o null). Al guardar enabled: false, la API apaga el flag en todas las cartas del vendedor en el mismo request (server-side), así no quedan publicaciones reprecándose por su cuenta.

Activación por carta o masiva. POST /listings/:id/auto-price (una carta) y POST /me/listings/auto-price/apply-all (todas las elegibles) prenden/apagan el flag. POST /me/listings/auto-price/recalc recalcula los precios al instante (sin esperar al job diario) y devuelve los precios aplicados junto con los contadores considered / noFreshPrice / belowMin. La faceta autoPriceEligibleCount (en /users/me/listings/facets) indica cuántas cartas activas de TCGplayer hay (lo que “activar en todas” alcanzaría).

Seguridad de la escritura. El reprecio es una escritura quirúrgica con guard CAS { status: active, autoPrice: true } y no toca el flujo de pago: las órdenes congelan el precio al crearse, así que un reprecio nunca altera una compra en curso. (Internamente el código se llama autoPrice / autoPricing; la marca visible es “Precios dinámicos”.)

MétodoPathAuthDescripción
GET/api/v1/me/cartuser JWTMi carrito enriquecido
POST/api/v1/me/cart/itemsuser JWT + activeAgrega item
PATCH/api/v1/me/cart/items/:listingIduser JWT + activeActualiza cantidad (0 = elimina)
DELETE/api/v1/me/cart/items/:listingIduser JWT + activeElimina item
DELETE/api/v1/me/cartuser JWT + activeVacía carrito
POST/api/v1/me/cart/checkoutuser JWT + activeCheckout del carrito (multi-vendedor)
POST/api/v1/me/checkout/confirm-redirectuser JWTConfirma un pago desde el redirect de MP (página de confirmación). Body { paymentId }

El carrito acepta items de varios vendedores. POST /me/cart/checkout lee el carrito autoritativo del servidor, agrupa por vendedor y bifurca:

  • 1 vendedorOrderService.createOrderWithPayment (flujo individual de siempre, reembolso a tarjeta vía MP) → responde { kind: 'order', orderId, mpInitPoint }.
  • 2+ vendedoresCheckoutService.createCartCheckout: crea una orden por vendedor + un agregado checkout, y un solo pago combinado en MP (top-up al wallet del comprador del que se fondea cada orden) → responde { kind: 'checkout', checkoutId, mpInitPoint }.

Mínimo de $1.500 por vendedor (no del total): si la parte de algún vendedor no lo alcanza, el checkout se rechaza nombrando al vendedor. El reembolso de una sub-orden grupal siempre va al wallet (ver decisiones → Pagos).

Confirmación post-pago (un solo camino para individual y combinado): todos los pagos de MP redirigen (back_urls) a la página /me/compras/confirmacion. Esa página toma el payment_id que MP agrega a la URL y llama a POST /me/checkout/confirm-redirect, que: verifica el pago contra MP (getPaymentno confía en los params de la URL, que son falsificables), valida que la orden/checkout sea del usuario (403 si no), confirma de forma idempotente reusando PaymentService/CheckoutService (mismo CAS que el webhook → si llegan ambos, el segundo es no-op) y devuelve el resumen. Así la confirmación no depende de esperar el webhook; el webhook queda como respaldo para cuando el comprador cierra la pestaña antes del redirect.

MétodoPathAuthDescripción
POST/api/v1/orders/checkoutuser JWT + activeCrea orden + MP preference
GET/api/v1/orders/:iduser JWTDetalle (buyer o seller)
POST/api/v1/orders/:id/resume-paymentuser JWT + activeReanuda pago si quedó en awaiting_payment
PATCH/api/v1/orders/:id/acceptuser JWT (seller)Acepta
PATCH/api/v1/orders/:id/shipuser JWT (seller)Marca como enviado
PATCH/api/v1/orders/:id/completeuser JWT (buyer)Confirma recepción
PATCH/api/v1/orders/:id/canceluser JWTCancela. Comprador solo en pending; en accepted solo el vendedor (reembolsa al comprador). Bloqueado en shipped/completed y si hay disputa abierta.
GET/api/v1/orders/:id/whatsappuser JWTDevuelve WhatsApp de la contraparte si activó esa preferencia
GET/api/v1/me/orders/buyeruser JWTMis compras (cada orden incluye counterpart = vendedor: {id, username, name, avatarUrl})
GET/api/v1/me/orders/selleruser JWTMis ventas (cada orden incluye counterpart = comprador: {id, username, name, avatarUrl})
MétodoPathAuthDescripción
GET/api/v1/me/unread-countuser JWT# de chats sin leer
GET/api/v1/me/conversationsuser JWTMis conversaciones recientes
GET/api/v1/conversations/:orderId/messagesuser JWTMensajes de la orden
POST/api/v1/conversations/:orderId/messagesuser JWTEnvía mensaje (rate-limited)
MétodoPathAuthDescripción
POST/api/v1/orders/:orderId/reviewuser JWTDeja review (después de completar)
GET/api/v1/orders/:orderId/reviewuser JWTMi review en esa orden
GET/api/v1/users/:username/reviewsnoneReviews recibidas por un seller
MétodoPathAuthDescripción
POST/api/v1/orders/:id/disputeuser JWTAbre dispute (buyer o seller)
GET/api/v1/orders/:id/disputeuser JWTDetalle dispute
POST/api/v1/orders/:id/dispute/responseuser JWTResponde dispute (la otra parte)
GET/api/v1/admin/disputesadminCola de disputes
GET/api/v1/admin/disputes/:idadminDetalle admin
POST/api/v1/admin/disputes/:id/resolveadminResuelve (outcome + adminNote)
MétodoPathAuthDescripción
POST/api/v1/reportsuser JWTReporta listing o user
GET/api/v1/admin/reportsadminLista agrupada
GET/api/v1/admin/reports/:idadminDetalle
MétodoPathAuthDescripción
POST/api/v1/uploads/signuser JWTFirma URL para subir a Cloudinary. kind: listing / proof / avatar / dispute / banner / dispute_response

El escáner identifica una carta a partir de una foto. El cliente captura el frame de la cámara, lo recorta al guía visual, lo comprime a JPEG (largo máx 1400px, calidad 0.75) y lo manda como data URL base64. El backend lo pasa por OCR.space (Engine 3), extrae el identificador (número de coleccionista) con una regex por TCG y resuelve contra el catálogo (cards). Ver el flujo completo y la migración desde pHash/Tesseract en decisiones técnicas.

MétodoPathAuthDescripción
POST/api/v1/scan/identify-imagenone (rate-limited)Identifica carta desde imagen. Body { tcg, imageBase64 }
GET/api/v1/admin/scanner-usageadminConsumo del cupo de OCR.space (hoy, mes, últimos 30 días, status)
  • Tiene body parser propio de 2 MB (express.json({ limit: '2mb' }) montado antes del global de 100 kb) y un rate limiter propio (scanMatchLimiter).
  • Body (validado con Zod):
    • tcg — slug del TCG (1-64 chars).
    • imageBase64 — data URL data:image/(jpeg|jpg|png|webp);base64,..., máx 1.5 M chars (~1 MB de imagen; tope del free tier de OCR.space). Sobre ese tamaño responde 400 sin gastar cuota.
  • Respuesta 200: { matches: ScanMatchEntry[] } (hasta 5).
    • ScanMatchEntry: { card, distance, confidence } con confidence: 'high' | 'medium'.
    • card: { id, tcg, baseName, setName, setSlug, number, rarity, imageUrl }.
    • matches: [] (vacío) → “no encontrada”.
  • Errores:
    • 400 INVALID_BODYtcg/imageBase64 inválidos.
    • 503 SCANNER_QUOTA_EXCEEDED — se agotó el cupo de OCR.space (free tier). El cliente muestra “no disponible, intenta más tarde”.
    • 502 OCR_UPSTREAM_ERROR — error upstream de OCR.space.
  • Tracking de cuota: cada request incrementa el contador del día en la colección scanner_usage antes de llamar a OCR (refleja “intentos consumidos” aunque el upstream falle).

Respuesta: { data: { today, thisMonth, last30Days: [{ date, count }], dailyLimit: 500, monthlyLimit: 25000, status } }, con status: 'ok' | 'warning' (≥80% diario) | 'critical' (≥100% diario).

MétodoPathAuthDescripción
GET/api/v1/bannersnoneBanners activos
GET/api/v1/admin/bannersadminTodos (admin)
GET/api/v1/admin/banners/:idadminDetalle
POST/api/v1/admin/bannersadminCrea
PATCH/api/v1/admin/banners/:idadminActualiza
POST/api/v1/admin/banners/reorderadminReordena
POST/api/v1/admin/banners/:id/publishadminPublica
POST/api/v1/admin/banners/:id/unpublishadminDespublica
POST/api/v1/admin/banners/:id/archiveadminArchiva
POST/api/v1/admin/banners/:id/restoreadminRestaura
MétodoPathAuthDescripción
GET/api/v1/me/walletuser JWTMi balance
GET/api/v1/me/wallet/entriesuser JWTMi ledger
GET/api/v1/admin/wallet-systemadminBalance + últimas 50 del system wallet
POST/api/v1/admin/wallet-system/owner-drawadminOwner extrae fondos del system wallet
GET/api/v1/admin/users/:userId/walletadminWallet de un user (auditado)
POST/api/v1/admin/users/:userId/wallet/creditadminAcredita
POST/api/v1/admin/users/:userId/wallet/debitadminDebita
POST/api/v1/admin/reconcileadminReconciliación manual
GET/api/v1/admin/finance/overviewadminSobres financieros (ganancia / IVA / usuarios / comisión MP / en tránsito), derivados
GET/api/v1/admin/finance/monthlyadminDesglose por mes (ventas / comisión / IVA / fee MP / ganancia neta)
GET/api/v1/admin/finance/movementsadminMovimientos de las cuentas de sistema (tcgcards + IVA)
POST/api/v1/admin/finance/iva-paymentadminRegistra un pago de IVA al SII (debita la cuenta de sistema de IVA)

Los endpoints de finanzas son read-only/derivados (calculan los sobres leyendo órdenes/payments/ledger; no tocan el flujo de pagos). La única escritura es iva-payment, sobre una cuenta de sistema separada. Ver decisión “Panel de finanzas” en decisiones.

MétodoPathAuthDescripción
GET/api/v1/me/bank-accountuser JWTMi cuenta (encrypted)
PUT/api/v1/me/bank-accountuser JWTCrea/actualiza
DELETE/api/v1/me/bank-accountuser JWTElimina
GET/api/v1/admin/users/:userId/bank-accountadminDecrypted (auditado)
MétodoPathAuthDescripción
POST/api/v1/me/withdrawalsuser JWTSolicita retiro
GET/api/v1/me/withdrawalsuser JWTMis retiros
GET/api/v1/admin/withdrawalsadminLista cola
GET/api/v1/admin/withdrawals/:idadminDetalle
POST/api/v1/admin/withdrawals/:id/completeadminMarca como pagado
POST/api/v1/admin/withdrawals/:id/canceladminCancela
MétodoPathAuthDescripción
POST/api/v1/webhooks/mpMP signatureWebhook de Mercado Pago. Maneja approvals/rejects/late refunds
MétodoPathAuthDescripción
POST/api/v1/admin/moderation/hide-listingadminOculta listing
POST/api/v1/admin/moderation/unhide-listingadminRestaura listing
POST/api/v1/admin/moderation/warn-useradminAdvierte user
POST/api/v1/admin/moderation/suspend-useradminSuspende (7/30/90/null días)
POST/api/v1/admin/moderation/unsuspend-useradminLevanta suspensión
POST/api/v1/admin/moderation/ban-useradminBanea permanente
POST/api/v1/admin/moderation/unban-useradminDesbanea
POST/api/v1/admin/moderation/dismiss-reportadminDesestima 1 report
POST/api/v1/admin/moderation/dismiss-target-reportsadminDesestima todos los reports de un target
MétodoPathAuthDescripción
GET/api/v1/admin/overviewadminDashboard overview
GET/api/v1/admin/ordersadminBúsqueda de órdenes
GET/api/v1/admin/orders/:idadminDetalle
GET/api/v1/admin/usersadminCola moderation
GET/api/v1/admin/users/:username/moderationadminInfo moderation

Estos endpoints son para sincronización del catálogo desde TCGplayer. Usan X-Admin-Token.

MétodoPathAuthDescripción
GET/api/v1/admin/statsadmin tokenStats por TCG
POST/api/v1/admin/sync/:tcgadmin tokenDispara sync (fire-and-forget)
MétodoPathAuthDescripción
POST/api/v1/users/upsertinternal authCrea/actualiza user desde Next.js auth bridge
GET/api/v1/users/meuser JWTMi perfil
PATCH/api/v1/users/meuser JWTEdita perfil
GET/api/v1/users/by-id/:idnonePerfil público por id
GET/api/v1/users/:usernamenonePerfil público por username
POST/api/v1/users/me/acknowledge-warninguser JWTReconoce advertencia
GET/api/v1/users/me/can-deleteuser JWTVerifica si puede eliminar cuenta
DELETE/api/v1/users/meuser JWTElimina cuenta (soft delete)

PATCH /users/me acepta rut (RUT chileno): se valida (módulo 11, acepta K) y se guarda normalizado ("12345678-5"). Es privado — no aparece en el perfil público (/users/:username); solo lo ve el vendedor en la “Información de entrega” de una venta con envío a domicilio (junto a nombre, teléfono y correo del comprador, todos capturados como snapshot en order.buyerAddress al comprar). El checkout de envío a domicilio se bloquea (en el front) si al comprador le faltan RUT/teléfono/dirección.

PATCH /users/me también acepta autoPricing: { enabled, usdRate } — la configuración maestra de Precios dinámicos del vendedor (usdRate = CLP por USD, int 1..100000 o null). Al guardar enabled: false, la API apaga el flag en todas las publicaciones del vendedor en el mismo request. Ver Precios dinámicos en la sección Listings.

MétodoPathAuthDescripción
POST/api/v1/internal/cron/orders-tickcron secretTick de órdenes (auto-cancel / auto-complete)
POST/api/v1/internal/cron/orders-awaiting-payment-cleanupcron secretCancela órdenes con timeout
POST/api/v1/internal/cron/wallet-reconcilecron secretReconcilia wallet
POST/api/v1/internal/cron/refunds-reconcilecron secretReconcilia refunds
  • Rate limits globales: 600 req/min. Endpoints con limiter propio: uploads, listings POST, chat, reviews, scan (scanMatchLimiter), Precios dinámicos (toggle por carta 60/min; apply-all y recalc 10/min).
  • Amounts: siempre CLP centavos (integer). Nunca pesos. Mostrarse con formatPriceCents().
  • Paginación:
    • ?page=N (offset-based) para queries simples.
    • ?cursor=<iso> o ?offset=<iso> (cursor-based) para feeds grandes.
  • Timestamps: ISO 8601 UTC.
  • Phone format: 569XXXXXXXX (E.164 Chile, privado).