{"openapi":"3.0.3","info":{"title":"vexorpay API","version":"1.0.0","description":"API pública de vexorpay para cobrar en USD desde Argentina y otros países. Los links de pago siempre operan en dólares y cobran en la cuenta de cobro del creador, que es el vendedor: cada venta paga una sola comisión de vexorpay (desde 2,7 % + USD 1, con el procesamiento incluido; más con tarjetas de algunos países o con conversión de moneda) y el resto se acredita en su saldo (cada venta trae el desglose en `feeCents`, `feeBreakdown` y `netCents`). Sin suscripción. En los pagos únicos el comprador puede pagar en su moneda local; las suscripciones se cobran en USD. No hay planes gratis ni precios por asiento. Producción (`https://api.vexorpay.com`) y sandbox (`https://api.sandbox.vexorpay.com`) son entornos separados, con sus propios usuarios y datos. Autenticación por API key con el header `Authorization: Bearer <key>`: el formato `vxp_live_<shard>_...` es de producción y `vxp_test_<shard>_...` de sandbox (las legacy `vxp_u_<shard>_...` y `vxp_live_...` equivalen a producción). El modo lo define la clave: no existe el parámetro `sandbox` en los requests, y cada host solo acepta claves de su modo (`api.vexorpay.com` solo live, `api.sandbox.vexorpay.com` solo test) — un desajuste responde `403 livemode_mismatch`. Además, el v1 acepta tokens OAuth de apps conectadas (formato `vxo_at_...`): con ellos el modo queda fijado por la conexión y no pasa por el gate de host; cada endpoint exige el scope que figura en su `security`; si la conexión no lo tiene responde `403 insufficient_scope` con `detail.required_scope`. Rate limit: 100 requests por minuto por API key o token (todas las respuestas incluyen los headers X-RateLimit-Limit/Remaining/Reset). Los POST de creación de links, reembolsos y retiros son idempotentes vía el header `Idempotency-Key`: misma clave + mismo body devuelve el recurso ya creado (200); misma clave + body distinto devuelve 409. Compatibilidad: los campos desconocidos en un body se ignoran (no validan) y no se devuelven en la respuesta (la unica excepcion son las keys desconocidas dentro de un plan de `prices[]`, que responden 400 validation_error); la API puede agregar campos nuevos a las respuestas en cualquier momento. Toda respuesta trae `Cache-Control: no-store` y `X-Request-Id`; los headers de rate limit también acompañan a los 401 y a los 429."},"servers":[{"url":"https://api.staging.vexorpay.com/api/v1"}],"tags":[{"name":"Health","description":"Estado del servicio."},{"name":"Payment links","description":"Links de pago (one-time y recurrentes)."},{"name":"Disputes","description":"Disputas (chargebacks) de las ventas: sólo se pueden aceptar."},{"name":"Subscriptions","description":"Suscripciones de los links recurrentes."},{"name":"Balance","description":"Saldo de la cuenta de cobro del creador."},{"name":"Orders","description":"Ventas, con el desglose de comisiones, y sus reembolsos."},{"name":"Customers","description":"Clientes (compradores), con sus totales, ventas y suscripciones."},{"name":"Payouts","description":"Retiros del saldo disponible al medio de retiro del creador."},{"name":"Events","description":"Historial de eventos de webhook para reconciliación."},{"name":"Connected accounts","description":"Cuentas conectadas a una app de marketplace (header `Vexor-Account`)."}],"security":[{"bearerAuth":[]}],"paths":{"/health":{"get":{"tags":["Health"],"operationId":"getHealth","summary":"Estado del servicio","description":"Liveness: responde `{ status: 'ok' }` sin tocar la base de datos ni proveedores externos. No requiere autenticación.","security":[],"responses":{"200":{"description":"Servicio operativo.","content":{"application/json":{"schema":{"type":"object","required":["status","service"],"properties":{"status":{"type":"string","example":"ok"},"service":{"type":"string","example":"vexorpay-api"}}}}}}}}},"/payment-links":{"post":{"tags":["Payment links"],"operationId":"createPaymentLink","summary":"Crear un link de pago","description":"Crea un link de pago one-time (con `amount`) o recurrente (con `type: recurring` y `prices[]` de 1 a 5 planes). Los montos van en dólares (USD) y el mínimo por cobro o plan es USD 4; no hay planes gratis (`amount: 0` → 400 `unsupported_plan`) ni precios por asiento (400 `unsupported_plan`). Idempotente con el header `Idempotency-Key`: un replay con el mismo body devuelve el link ya creado (200). El link cobra en la cuenta de cobro del creador: sin la cuenta activada responde 409 `payment_account_not_ready` (se puede crear con la verificación de identidad pendiente). Cada venta paga la comisión de vexorpay (desde 2,7 % + USD 1, con el procesamiento incluido). Con el header `Vexor-Account` el link se crea a nombre del usuario conectado.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"},{"$ref":"#/components/parameters/VexorAccount"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLinkCreate"}}}},"responses":{"200":{"description":"Replay de idempotencia: la misma `Idempotency-Key` con el mismo body devuelve el link previamente creado (mismo cuerpo que el 201).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLink"}}}},"201":{"description":"Link de pago creado: misma forma que `GET /payment-links/{id}` (incluye `sandbox`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLinkCreated"}}}},"400":{"description":"Error de validación: invalid_request, minimum_amount, invalid_amount, prices_required, too_many_prices, validation_error, invalid_interval, invalid_interval_count, invalid_trial o unsupported_plan (plan gratis o precio por asiento).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"invalid_connected_account: el header `Vexor-Account` apunta a una cuenta sin conexión activa de la app (o la credencial no es de una app de marketplace). insufficient_scope: la conexión OAuth no tiene el scope `account:operate`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"idempotency_conflict: la `Idempotency-Key` ya se usó con un body distinto. payment_account_not_ready: la cuenta de cobro del creador no está activada (o fue rechazada).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"payment_link_failed: el procesador de pagos rechazó la creación del link (no quedó nada publicado, se puede reintentar).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[]},{"oauth":["links:write"]}]},"get":{"tags":["Payment links"],"operationId":"listPaymentLinks","summary":"Listar links de pago","description":"Lista los links del usuario con paginación por cursor. Excluye siempre los links borrados. Orden descendente por fecha de creación.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["active","canceled","archived"]},"description":"Filtra por estado. Sin parámetro incluye todos."},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["one_time","recurring"]},"description":"Filtra por tipo de link."},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Lista paginada de links.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLinkList"}}}},"400":{"description":"validation_error: status o type inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"security":[{"bearerAuth":[]},{"oauth":["read"]}]}},"/payment-links/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ID del link de pago."}],"get":{"tags":["Payment links"],"operationId":"getPaymentLink","summary":"Ver un link de pago","description":"Devuelve el detalle de un link. `url` es la página pública para compartir y `checkoutUrl` el checkout alojado del link.","responses":{"200":{"description":"Detalle del link de pago.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLink"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"link_not_found: no existe o pertenece a otro usuario.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[{"bearerAuth":[]},{"oauth":["read"]}]},"patch":{"tags":["Payment links"],"operationId":"updatePaymentLink","summary":"Actualizar un link de pago","description":"Actualiza campos parciales de un link. Se editan `title` (no se puede vaciar: 400 `validation_error`), `description`, `image_file_id`, `reference`, `amount` (pago único: aplica a las compras nuevas) y, en links recurrentes, el nombre (`description`) de cada plan existente (cada plan va con su `id`; sin `prices` los planes no cambian). Cambiar el precio, el intervalo o la prueba gratis de un plan, o agregar o quitar planes, responde 400 `unsupported_plan`: hay que crear un link nuevo. El `type` es inmutable (400 `validation_error`). `status` acepta `active` o `archived` (otro valor → 400 `validation_error`); un link archivado no se edita ni se reactiva (400 `payment_link_inactive`).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLinkUpdate"}}}},"responses":{"200":{"description":"Link actualizado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLink"}}}},"400":{"description":"Errores de validación: invalid_request (body vacío), invalid_amount, validation_error, prices_required, payment_link_inactive, minimum_amount, invalid_interval, invalid_interval_count, invalid_trial o unsupported_plan (cambio de planes de un link recurrente).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"link_not_found: no existe o pertenece a otro usuario.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"$ref":"#/components/responses/PaymentLinkFailed"}},"security":[{"bearerAuth":[]},{"oauth":["links:write"]}]},"delete":{"tags":["Payment links"],"operationId":"deletePaymentLink","summary":"Eliminar un link de pago","description":"Alineado con la acción Eliminar del dashboard: saca el link de venta y nunca borra ventas ya realizadas. El link se archiva (`status: archived`): `url` y `checkoutUrl` dejan de funcionar; las suscripciones vigentes siguen cobrándose hasta que se cancelen. Un link ya `deleted` o `archived` responde 409 `already_processed` (no es idempotente).","responses":{"200":{"description":"Link archivado (`status: archived`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLink"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"link_not_found: no existe o pertenece a otro usuario.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"already_processed: el link ya fue eliminado o archivado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"security":[{"bearerAuth":[]},{"oauth":["links:write"]}]}},"/payment-links/{id}/stats":{"get":{"tags":["Payment links"],"operationId":"getPaymentLinkStats","summary":"Métricas de un link","description":"Las métricas del detalle del link en el dashboard. Montos en centavos de USD y NETOS del creador (sin impuesto, comisiones ni reembolsos), sobre las ventas del link. La forma depende de `type`: `one_time` o `recurring`.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Métricas.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLinkStats"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"link_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"security":[{"bearerAuth":[]},{"oauth":["read"]}]}},"/payment-links/{id}/checkout":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ID del link de pago activo que se va a pagar."}],"post":{"tags":["Payment links"],"operationId":"createPaymentLinkCheckout","summary":"Generar un checkout para un link","description":"Crea una sesión de checkout para un link activo y devuelve su URL. Cada llamada crea una sesión nueva (no usa `Idempotency-Key`); la venta llega después como orden (`GET /orders`, evento `payment.completed`). En links recurrentes lleva directo al plan indicado (`price_id` o el default). `metadata` sólo admite valores planos; `reference` y `metadata` vuelven en la venta (`GET /orders`), en sus webhooks y en `/events`. Producción y sandbox nunca se mezclan: el link debe ser del mismo modo que la clave. Con el header `Vexor-Account` el checkout se crea sobre un link del usuario conectado.","parameters":[{"$ref":"#/components/parameters/VexorAccount"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutInput"}}}},"responses":{"200":{"description":"Sesión de checkout creada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutResponse"}}}},"400":{"description":"invalid_request, validation_error (p. ej. `price_id` en un link de pago único) o prices_required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"invalid_connected_account: el header `Vexor-Account` apunta a una cuenta sin conexión activa de la app (o la credencial no es de una app de marketplace). insufficient_scope: la conexión OAuth no tiene el scope `account:operate`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"link_not_found (no existe, no está activo o es de otro modo) o price_not_found (plan inexistente en links recurrentes).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"payment_account_not_ready: la cuenta de cobro del dueño del link no está activada (o fue rechazada).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"$ref":"#/components/responses/UpstreamError"}},"security":[{"bearerAuth":[]},{"oauth":["charges:write"]}]}},"/disputes":{"get":{"tags":["Disputes"],"operationId":"listDisputes","summary":"Listar disputas","description":"Lista las disputas (chargebacks) de las ventas del usuario con paginación por cursor, orden descendente por fecha de creación. La venta es del creador, así que la disputa también: si se pierde o se acepta, el monto y la fee de USD 15 se descuentan de su saldo. Llegan solas (webhooks `dispute.created` y `dispute.updated`). El modo (prueba o producción) lo fija la clave.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["warning_needs_response","warning_under_review","warning_closed","needs_response","under_review","won","lost","closed"]},"description":"Filtra por estado de la disputa."},{"name":"order_id","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra por la venta disputada (ID de `GET /orders`)."},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Lista paginada de disputas.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisputeList"}}}},"400":{"description":"validation_error: status inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"security":[{"bearerAuth":[]},{"oauth":["read"]}]}},"/disputes/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ID de la disputa."}],"get":{"tags":["Disputes"],"operationId":"getDispute","summary":"Ver una disputa","description":"Detalle de una disputa. Desde vexorpay la única acción posible es aceptarla (`POST /disputes/{id}/accept`); por ahora no se envía evidencia.","responses":{"200":{"description":"Detalle de la disputa.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Dispute"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"dispute_not_found: no existe o pertenece a otro usuario.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"security":[{"bearerAuth":[]},{"oauth":["read"]}]}},"/customers":{"get":{"tags":["Customers"],"operationId":"listCustomers","summary":"Listar clientes","description":"Lista los clientes (compradores) del creador, de la compra más reciente a la más vieja, con sus totales: compras, total pagado (neto de reembolsos), suscripciones activas y primera y última compra. Los clientes se derivan de las ventas.","parameters":[{"name":"email","in":"query","required":false,"schema":{"type":"string"},"description":"Email exacto (sin distinguir mayúsculas)."},{"name":"query","in":"query","required":false,"schema":{"type":"string"},"description":"Busca en el email o el nombre (contiene)."},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Lista paginada de clientes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerList"}}}},"400":{"description":"validation_error: `limit` inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"security":[{"bearerAuth":[]},{"oauth":["read"]}]}},"/customers/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ID del cliente tal como lo devuelve la API (valor opaco; codificarlo en la URL)."}],"get":{"tags":["Customers"],"operationId":"getCustomer","summary":"Ver un cliente","description":"Devuelve un cliente con sus totales, sus ventas (`orders`, hasta 100, más nuevas primero) y sus suscripciones.","responses":{"200":{"description":"El cliente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"customer_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"security":[{"bearerAuth":[]},{"oauth":["read"]}]}},"/orders":{"get":{"tags":["Orders"],"operationId":"listOrders","summary":"Listar ventas","description":"Lista las ventas (pagos únicos, altas de suscripción y renovaciones), de la más nueva a la más vieja, con el desglose de comisiones de cada una.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["paid","partially_refunded","refunded","pending","failed"]}},{"name":"payment_link_id","in":"query","required":false,"schema":{"type":"string"},"description":"Ventas de un link."},{"name":"reference","in":"query","required":false,"schema":{"type":"string"},"description":"Ventas con esta referencia del integrador (coincidencia exacta)."},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Lista paginada de ventas.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderList"}}}},"400":{"description":"validation_error: status inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"security":[{"bearerAuth":[]},{"oauth":["read"]}]}},"/orders/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ID de la venta."}],"get":{"tags":["Orders"],"operationId":"getOrder","summary":"Ver una venta","description":"Devuelve una venta con su desglose (total, impuesto, comisión de vexorpay y neto).","responses":{"200":{"description":"La venta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"order_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"security":[{"bearerAuth":[]},{"oauth":["read"]}]}},"/orders/{id}/refund":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ID de la venta."}],"post":{"tags":["Orders"],"operationId":"refundOrder","summary":"Reembolsar una venta","description":"Reembolsa total o parcialmente una venta `paid` o `partially_refunded`. El dinero sale del saldo del creador. Irreversible. Idempotente con `Idempotency-Key` (un replay devuelve la venta sin volver a reembolsar). Dispara el webhook `payment.refunded`.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRefundInput"}}}},"responses":{"200":{"description":"La venta con el reembolso aplicado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"400":{"description":"validation_error: campo desconocido, monto fuera de rango o la venta no está pagada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"insufficient_scope: la conexión OAuth no tiene `charges:write`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"order_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"idempotency_conflict.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"$ref":"#/components/responses/UpstreamError"}},"security":[{"bearerAuth":[]},{"oauth":["charges:write"]}]}},"/disputes/{id}/accept":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ID de la disputa."}],"post":{"tags":["Disputes"],"operationId":"acceptDispute","summary":"Aceptar (dar por perdida) una disputa","description":"Da la disputa por perdida: el monto disputado no vuelve y la fee de la disputa queda a cargo del creador. Es la única acción posible sobre una disputa desde vexorpay. Irreversible.","responses":{"200":{"description":"La disputa actualizada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Dispute"}}}},"400":{"description":"dispute_not_respondable: la disputa ya está cerrada o no se puede aceptar.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"insufficient_scope: la conexión OAuth no tiene `disputes:write`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"dispute_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"$ref":"#/components/responses/UpstreamError"}},"security":[{"bearerAuth":[]},{"oauth":["disputes:write"]}]}},"/subscriptions":{"get":{"tags":["Subscriptions"],"operationId":"listSubscriptions","summary":"Listar suscripciones","description":"Lista las suscripciones del usuario con paginación por cursor, orden descendente por fecha de creación.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["trialing","active","past_due","canceled","expired"]},"description":"Filtra por estado de la suscripción."},{"name":"payment_link_id","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra por link de pago."},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Lista paginada de suscripciones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionList"}}}},"400":{"description":"validation_error: status inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"security":[{"bearerAuth":[]},{"oauth":["read"]}]}},"/subscriptions/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ID de la suscripción."}],"get":{"tags":["Subscriptions"],"operationId":"getSubscription","summary":"Ver una suscripción","description":"Devuelve el detalle de una suscripción del usuario.","responses":{"200":{"description":"Detalle de la suscripción.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subscription"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"subscription_not_found: no existe o pertenece a otro usuario.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"security":[{"bearerAuth":[]},{"oauth":["read"]}]}},"/subscriptions/{id}/cancel":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ID de la suscripción."}],"post":{"tags":["Subscriptions"],"operationId":"cancelSubscription","summary":"Cancelar una suscripción al final del período","description":"Programa la cancelación de la suscripción al final del período actual (`cancelAtPeriodEnd: true`): el cliente conserva el acceso hasta el fin del ciclo pago y no se le vuelve a cobrar. Idempotente: cancelar una suscripción ya cancelada o con cancelación programada devuelve 200 con su estado actual.","responses":{"200":{"description":"Suscripción con la cancelación programada (o su estado actual si ya estaba cancelada).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subscription"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"subscription_not_found: no existe o pertenece a otro usuario.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"$ref":"#/components/responses/UpstreamError"}},"security":[{"bearerAuth":[]},{"oauth":["subscriptions:write"]}]}},"/subscriptions/{id}/portal":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ID de la suscripción."}],"post":{"tags":["Subscriptions"],"operationId":"openSubscriptionPortal","summary":"Obtener la página de gestión del suscriptor","description":"Devuelve un link de acceso directo al portal del comprador de vexorpay, limitado a esta suscripción y válido por 24 horas: ahí el suscriptor cambia su tarjeta, cancela al fin del período (o deshace la cancelación) y baja sus recibos. Conviene pedirlo en el momento de redirigir al comprador.","responses":{"200":{"description":"URL de la página de gestión.","content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"URL a la que hay que redirigir al suscriptor."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"subscription_not_found: no existe o pertenece a otro usuario.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"$ref":"#/components/responses/UpstreamError"}},"security":[{"bearerAuth":[]},{"oauth":["subscriptions:write"]}]}},"/balance":{"get":{"tags":["Balance"],"operationId":"getBalance","summary":"Ver el saldo","description":"Devuelve el saldo de la cuenta de cobro del creador en centavos de USD: disponible para retirar, pendiente de liberarse y reserva (si se informa). Las ventas se acreditan ya descontadas la comisión de vexorpay y las de procesamiento.","responses":{"200":{"description":"Saldo de la cuenta de cobro.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Balance"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"$ref":"#/components/responses/PaymentAccountNotReady"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"$ref":"#/components/responses/UpstreamError"}},"security":[{"bearerAuth":[]},{"oauth":["read"]}]}},"/payouts":{"get":{"tags":["Payouts"],"operationId":"listPayouts","summary":"Listar retiros","description":"Lista los retiros del creador, del más nuevo al más viejo. Sólo lectura: los retiros se piden desde el portal de retiros de la cuenta de cobro (dashboard).","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Lista paginada de retiros.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayoutList"}}}},"400":{"description":"validation_error: cursor de paginación inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"security":[{"bearerAuth":[]},{"oauth":["read"]}]}},"/events":{"get":{"tags":["Events"],"operationId":"listEvents","summary":"Listar eventos de webhook","description":"Lista los eventos de negocio del usuario (payment.completed, payment.refunded, subscription.created/updated/canceled, dispute.created/updated) en orden cronológico descendente para reconciliación. Cada evento usa el mismo id determinístico (`evt_...`) que se firma en los webhooks salientes, así el integrador puede cruzar lo recibido por POST con el histórico y deduplicar. `data.object` es el recurso (Order, Subscription o Dispute) con la misma forma que en la API y en el webhook.","parameters":[{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["payment.completed","payment.refunded","subscription.created","subscription.updated","subscription.canceled","dispute.created","dispute.updated"]},"description":"Filtra por tipo de evento."},{"name":"created[gte]","in":"query","required":false,"schema":{"type":"string"},"description":"Incluye solo eventos creados en esta fecha ISO 8601 o después (comparación lexicográfica)."},{"name":"created[lte]","in":"query","required":false,"schema":{"type":"string"},"description":"Incluye solo eventos creados en esta fecha ISO 8601 o antes (comparación lexicográfica)."},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Lista paginada de eventos.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventList"}}}},"400":{"description":"validation_error: tipo de evento inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"security":[{"bearerAuth":[]},{"oauth":["read"]}]}},"/connected-accounts":{"get":{"tags":["Connected accounts"],"operationId":"listConnectedAccounts","summary":"Listar cuentas conectadas","description":"Lista las conexiones ACTIVAS de la app de marketplace dueña de la credencial, con paginación por cursor. Solo opera la propia app (el `appId` sale de la credencial, nunca del request) y el modo lo define la credencial. Requiere una credencial de app de marketplace y, con tokens OAuth, el scope `account:operate`; una API key que no sea de una app responde 403 `invalid_connected_account`.","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/StartingAfter"},{"$ref":"#/components/parameters/EndingBefore"}],"responses":{"200":{"description":"Lista paginada de cuentas conectadas activas.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectedAccountList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"invalid_connected_account: la credencial no pertenece a una app de marketplace. insufficient_scope: la conexión OAuth no tiene el scope `account:operate`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"security":[{"bearerAuth":[]},{"oauth":["account:operate"]}]}},"/connected-accounts/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ID de la conexión (`AppConnection.id`)."}],"get":{"tags":["Connected accounts"],"operationId":"getConnectedAccount","summary":"Ver una cuenta conectada","description":"Devuelve el detalle de una conexión (activa o revocada) de la app. Responde `404 not_found` si no existe o es de otra app/modo.","responses":{"200":{"description":"Detalle de la conexión.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectedAccount"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"invalid_connected_account: la credencial no pertenece a una app de marketplace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found: no existe esa conexión en la app.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"security":[{"bearerAuth":[]},{"oauth":["account:operate"]}]},"delete":{"tags":["Connected accounts"],"operationId":"revokeConnectedAccount","summary":"Revocar una cuenta conectada","description":"Revoca la conexión (idempotente): revoca los tokens OAuth del conectado para esa app, borra el consentimiento y marca la conexión como `revoked`. Revocar una conexión ya revocada devuelve el mismo estado (200).","responses":{"200":{"description":"Conexión revocada (o ya revocada).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectedAccount"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"invalid_connected_account: la credencial no pertenece a una app de marketplace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found: no existe esa conexión en la app.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"security":[{"bearerAuth":[]},{"oauth":["account:operate"]}]}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key con formato `vxp_live_<shard>_...` (producción) o `vxp_test_<shard>_...` (sandbox); las legacy `vxp_u_<shard>_...` y `vxp_live_...` equivalen a producción. Mandar como `Authorization: Bearer <api_key>`."},"oauth":{"type":"oauth2","description":"Token OAuth de una app conectada (formato `vxo_at_...`). El scope de cada operación figura en su `security`; fija además el modo (sandbox o producción) de la conexión; al autenticarse por OAuth no hay gate de host.","flows":{"authorizationCode":{"authorizationUrl":"https://www.staging.vexorpay.com/oauth/authorize","tokenUrl":"https://www.staging.vexorpay.com/oauth/token","scopes":{"read":"Lectura de todos los recursos del usuario.","links:write":"Crear, actualizar y eliminar links de pago.","charges:write":"Crear checkouts y reembolsar ventas.","subscriptions:write":"Cancelar suscripciones y obtener la página de gestión del suscriptor.","disputes:write":"Aceptar disputas (darlas por perdidas).","account:operate":"Operar cuentas conectadas (marketplace): crear links y checkouts a nombre del conectado y gestionar las conexiones.","offline_access":"Emite refresh token para renovar el acceso (autorization_code)."}}}}},"headers":{"RateLimitLimit":{"description":"Cantidad máxima de requests por minuto para la API key (siempre 100).","schema":{"type":"integer"}},"RateLimitRemaining":{"description":"Requests restantes en la ventana de un minuto.","schema":{"type":"integer"}},"RateLimitReset":{"description":"Timestamp Unix (segundos) en el que se reinicia la ventana del rate limit.","schema":{"type":"integer"}},"RetryAfter":{"description":"Segundos a esperar antes de reintentar tras un 429.","schema":{"type":"integer"}}},"parameters":{"Limit":{"name":"limit","in":"query","required":false,"description":"Cantidad máxima de elementos por página (entero entre 1 y 100, default 10). Fuera de rango responde 400 validation_error.","schema":{"type":"integer","minimum":1,"maximum":100,"default":10}},"StartingAfter":{"name":"starting_after","in":"query","required":false,"description":"ID del cursor: devuelve los elementos posteriores a este id (paginación hacia adelante).","schema":{"type":"string"}},"EndingBefore":{"name":"ending_before","in":"query","required":false,"description":"ID del cursor: devuelve los elementos anteriores a este id (paginación hacia atrás).","schema":{"type":"string"}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Clave de idempotencia (máx. 255 caracteres). Misma clave + mismo body devuelve el recurso ya creado (200); misma clave + body distinto responde 409. Se conserva 24 h: después la clave se descarta y la operación corre de nuevo.","schema":{"type":"string"}},"VexorAccount":{"name":"Vexor-Account","in":"header","required":false,"description":"ID del usuario conectado (`connected_user_id`) para operar en su nombre. Requiere una conexión activa de la app dueña de la credencial y, con tokens OAuth, el scope `account:operate`. La operación se atribuye a la app; no hay comisión de marketplace para la app (la venta es del conectado, con las comisiones de cualquier venta). Si no hay conexión activa responde 403 `invalid_connected_account`.","schema":{"type":"string"}}},"responses":{"Unauthorized":{"description":"No autenticado. `unauthorized`: falta el header Authorization. `invalid_key`: API key inválida o revocada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"idempotency_conflict: la `Idempotency-Key` ya se usó con un body distinto al actual.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"rate_limited: superaste el límite de 100 requests por minuto.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ServerError":{"description":"unexpected_error: falla interna inesperada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UpstreamError":{"description":"upstream_error: el procesador de pagos rechazó o no respondió la operación (checkout, reembolso, cancelación, disputa, saldo o retiro). No se cambió nada: se puede reintentar.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentLinkFailed":{"description":"payment_link_failed: el procesador de pagos rechazó o no respondió la creación o la edición del link. No se cambió nada: se puede reintentar.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentAccountNotReady":{"description":"payment_account_not_ready: la cuenta de cobro no está lista (no se activó, fue rechazada o, para retirar, falta verificar la identidad o configurar el medio de retiro). Se resuelve desde el dashboard.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","description":"Código de error estable (ej. validation_error, link_not_found, idempotency_conflict).","example":"validation_error"},"message":{"type":"string","description":"Descripción legible del error en inglés (English)."},"detail":{"type":"object","additionalProperties":true,"description":"Detalle opcional del error, siempre un objeto: `resetInSeconds` en 429, `required_scope` en 403 `insufficient_scope`. Nunca expone mensajes crudos de excepciones internas."}}},"PaymentLink":{"type":"object","description":"Link de pago serializado por `paymentLinkToJson` (GET/lists).","required":["id","amountCents","currency","title","description","image","reference","metadata","status","slug","sandbox","url","createdAt","updatedAt","type"],"properties":{"id":{"type":"string"},"amountCents":{"type":"integer","description":"Monto en centavos de USD."},"currency":{"type":"string","example":"usd"},"title":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"image":{"type":"string","nullable":true,"description":"URL absoluta de la imagen del producto."},"reference":{"type":"string","nullable":true,"description":"Referencia interna del creador."},"metadata":{"type":"object","nullable":true,"additionalProperties":true,"description":"Metadatos libres enviados al crear el link."},"status":{"type":"string","enum":["active","canceled","archived","deleted"]},"slug":{"type":"string"},"sandbox":{"type":"boolean"},"url":{"type":"string","description":"URL pública `/p/{slug}` del link. Cada visita genera una referencia única para la compra (`VXP-…`), que vuelve en la venta, sus webhooks y `/events`; `?reference=<valor>` la reemplaza por la del integrador y `utm_source`, `utm_medium`, `utm_campaign`, `utm_content` y `utm_term` (quedan en la `metadata` de la venta)."},"checkoutUrl":{"type":"string","nullable":true,"description":"Checkout alojado del link (sin referencia propia). Para compartir conviene `url`."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"type":{"type":"string","enum":["one_time","recurring"],"default":"one_time"},"prices":{"type":"array","description":"Planes del link recurrente (solo si tiene precios).","items":{"$ref":"#/components/schemas/SubscriptionPrice"}}}},"PaymentLinkCreated":{"type":"object","description":"Cuerpo de 201 de POST /payment-links: misma forma que `PaymentLink` (incluye `sandbox`).","required":["id","amountCents","currency","title","description","reference","metadata","status","slug","sandbox","url","image","createdAt","updatedAt","type"],"properties":{"id":{"type":"string"},"amountCents":{"type":"integer"},"currency":{"type":"string","example":"usd"},"title":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"reference":{"type":"string","nullable":true},"metadata":{"type":"object","nullable":true,"additionalProperties":true},"status":{"type":"string","enum":["active","canceled","archived"]},"slug":{"type":"string"},"sandbox":{"type":"boolean","description":"true si es un link del modo de prueba (sandbox)."},"url":{"type":"string"},"image":{"type":"string","nullable":true},"checkoutUrl":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"type":{"type":"string","enum":["one_time","recurring"]},"prices":{"type":"array","items":{"$ref":"#/components/schemas/SubscriptionPrice"}}}},"SubscriptionPrice":{"type":"object","description":"Plan de un link recurrente.","required":["id","amountCents","currency","interval","intervalCount","trialPeriodDays","description","isDefault"],"properties":{"id":{"type":"string"},"amountCents":{"type":"integer","description":"Precio del ciclo en centavos de USD."},"currency":{"type":"string","example":"usd"},"interval":{"type":"string","enum":["day","week","month","year"]},"intervalCount":{"type":"integer","description":"Cada cuántos intervalos se cobra (1-12)."},"trialPeriodDays":{"type":"integer","nullable":true},"description":{"type":"string","nullable":true},"isDefault":{"type":"boolean"}}},"PaymentLinkCreate":{"type":"object","required":["title"],"properties":{"amount":{"type":"number","description":"Monto en dólares (USD), requerido para links one-time. Mínimo 4. Ignorado cuando `type` es recurring.","minimum":4,"example":25},"title":{"type":"string","minLength":1,"description":"Título del producto: el nombre que ve el comprador en el checkout y en el recibo. Obligatorio (vacío o sólo espacios → 400 `validation_error`)."},"description":{"type":"string"},"image_file_id":{"type":"string","description":"ID de un archivo GridFS completado del usuario (foto del producto). Se muestra en la página del link y en el checkout."},"reference":{"type":"string","description":"Referencia interna del creador."},"metadata":{"type":"object","additionalProperties":true,"description":"Metadatos libres de la compra."},"type":{"type":"string","enum":["one_time","recurring"],"default":"one_time"},"prices":{"type":"array","maxItems":5,"description":"Planes del link recurrente (requerido cuando `type` es recurring).","items":{"$ref":"#/components/schemas/PricePlanInput"}}}},"PricePlanInput":{"type":"object","description":"Definición de un plan de link recurrente. Keys en snake_case; montos en dólares (se convierten a centavos). El monto mínimo por plan es USD 4: no hay planes gratis. No hay precio por asiento (una suscripción es de una unidad): `pricing_type: per_seat` o campos de asientos responden 400 `unsupported_plan`. Una key desconocida responde 400 `validation_error`.","required":["amount","interval"],"properties":{"id":{"type":"string","description":"ID del plan existente (solo al editar con PATCH: actualiza ese mismo plan)."},"amount":{"type":"number","description":"Precio del ciclo en dólares, mínimo 4.","minimum":4},"currency":{"type":"string","enum":["usd"],"default":"usd"},"interval":{"type":"string","enum":["day","week","month","year"]},"interval_count":{"type":"integer","default":1,"description":"Cada cuántos intervalos (1-12; anual solo cada 1)."},"trial_period_days":{"type":"integer","description":"Días de prueba gratis (1-90)."},"description":{"type":"string"}}},"PaymentLinkUpdate":{"type":"object","description":"Cuerpo de PATCH /payment-links/{id}. Solo los campos enviados se actualizan.","properties":{"amount":{"type":"number","description":"Nuevo monto en dólares (solo one-time), mínimo 4.","minimum":4},"title":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"image_file_id":{"type":"string","nullable":true,"description":"ID de una imagen del usuario; null quita la foto actual."},"reference":{"type":"string","nullable":true},"status":{"type":"string","enum":["active","archived"],"description":"`archived` saca el link de venta (igual que DELETE)."},"prices":{"type":"array","maxItems":5,"description":"Planes del link (solo recurring): la misma lista de planes, cada uno con su `id`, y sólo se cambia el nombre (`description`). Cambiar precio, intervalo, prueba gratis o la cantidad de planes responde 400 `unsupported_plan`.","items":{"$ref":"#/components/schemas/PricePlanInput"}}}},"CheckoutInput":{"type":"object","properties":{"reference":{"type":"string","description":"Referencia del integrador (p. ej. su id de orden), hasta 200 caracteres; si se omite, se genera una única (`VXP-…`). Vuelve en la venta (`reference` de `GET /orders`), en sus webhooks y en `/events`."},"metadata":{"type":"object","additionalProperties":true,"description":"Metadatos de la compra. Sólo valores planos (string, número o booleano). Vuelven en la `metadata` de la venta."},"customer_email":{"type":"string","format":"email","description":"Email del comprador para precargar el checkout (debe incluir '@')."},"price_id":{"type":"string","description":"ID del plan al que llevar al comprador (solo recurrente). Ausente = plan default."}}},"CheckoutResponse":{"type":"object","description":"Respuesta de checkout: una sesión de checkout por llamada.","required":["checkoutId","reference","price_id","checkoutUrl","expiresAt"],"properties":{"checkoutId":{"type":"string","description":"ID de la sesión de checkout. Vuelve en la venta (`checkoutId` de `GET /orders`)."},"reference":{"type":"string","description":"Referencia de la venta: la `reference` enviada o, si se omitió, una única generada (`VXP-…`). Vuelve en la orden, sus webhooks y `/events`."},"price_id":{"type":"string","nullable":true,"description":"Plan elegido (null en links de pago único)."},"checkoutUrl":{"type":"string","description":"URL del checkout alojado a la que hay que redirigir al comprador."},"expiresAt":{"type":"string","format":"date-time","nullable":true,"description":"Vencimiento de la sesión (null si no vence)."}}},"Dispute":{"type":"object","description":"Disputa (chargeback) de una venta. Es del creador: si se pierde o se acepta, el monto y la fee de USD 15 se descuentan de su saldo. Desde vexorpay sólo se puede aceptar.","required":["id","orderId","paymentLinkId","paymentLinkUrl","reference","customerEmail","status","statusLabel","reason","reasonLabel","amountCents","currency","feeCents","evidenceDeadline","daysLeft","isOpen","canAccept","outcome","sandbox","createdAt","resolvedAt"],"properties":{"id":{"type":"string","description":"ID de la disputa (usar en /disputes/{id})."},"orderId":{"type":"string","nullable":true,"description":"La venta disputada (GET /orders/{id})."},"paymentLinkId":{"type":"string","nullable":true},"paymentLinkUrl":{"type":"string","nullable":true},"reference":{"type":"string","nullable":true,"description":"Referencia de la venta disputada."},"customerEmail":{"type":"string","nullable":true},"status":{"type":"string","enum":["warning_needs_response","warning_under_review","warning_closed","needs_response","under_review","won","lost","closed"],"description":"Abiertas: `warning_needs_response`, `warning_under_review` (alerta previa), `needs_response`, `under_review`. Cerradas: `warning_closed`, `won`, `lost`, `closed`."},"statusLabel":{"type":"string","description":"Estado en texto, en español."},"reason":{"type":"string","nullable":true,"description":"Motivo informado (p. ej. `fraudulent`, `product_not_received`)."},"reasonLabel":{"type":"string","description":"Motivo para mostrar (`Sin motivo` si no se informa)."},"amountCents":{"type":"integer","description":"Monto disputado en centavos de USD."},"currency":{"type":"string","example":"usd"},"feeCents":{"type":"integer","description":"Fee de la disputa en centavos (USD 15), que se descuenta del saldo del creador."},"evidenceDeadline":{"type":"string","format":"date-time","nullable":true,"description":"Plazo de respuesta (null si no aplica)."},"daysLeft":{"type":"integer","nullable":true,"description":"Días restantes para responder (null si venció o no hay plazo)."},"isOpen":{"type":"boolean","description":"true si la disputa sigue abierta."},"canAccept":{"type":"boolean","description":"true si se puede aceptar (dar por perdida): `needs_response` o `warning_needs_response`."},"outcome":{"type":"string","nullable":true,"enum":["won","lost",null],"description":"Desenlace: `won`, `lost` o null mientras la disputa siga abierta."},"sandbox":{"type":"boolean","description":"true si la disputa es del modo de prueba."},"createdAt":{"type":"string","format":"date-time"},"resolvedAt":{"type":"string","format":"date-time","nullable":true}}},"DisputeList":{"type":"object","required":["data","has_more","total_count","url"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Dispute"}},"has_more":{"type":"boolean"},"total_count":{"type":"integer"},"url":{"type":"string"}}},"Subscription":{"type":"object","description":"Suscripción a un plan de un link recurrente. Siempre de una unidad; se cobra en USD cada ciclo.","required":["id","paymentLinkId","priceId","status","amountCents","interval","intervalCount","currentPeriodStart","currentPeriodEnd","cancelAtPeriodEnd","canceledAt","customerEmail","manageUrl","sandbox","createdAt"],"properties":{"id":{"type":"string"},"paymentLinkId":{"type":"string","nullable":true},"priceId":{"type":"string","nullable":true,"description":"Plan del link (`prices[].id`)."},"status":{"type":"string","description":"`trialing`, `active`, `past_due` (falló el último cobro), `canceled` o `expired`. Cualquier otro valor se trata como no activo.","example":"active"},"amountCents":{"type":"integer","description":"Precio por ciclo en centavos de USD."},"interval":{"type":"string","enum":["day","week","month","year"]},"intervalCount":{"type":"integer"},"currentPeriodStart":{"type":"string","format":"date-time","nullable":true},"currentPeriodEnd":{"type":"string","format":"date-time","nullable":true,"description":"Fin del ciclo en curso (en una prueba gratis, el fin de la prueba)."},"cancelAtPeriodEnd":{"type":"boolean","description":"true si se cancela al terminar el ciclo en curso."},"canceledAt":{"type":"string","format":"date-time","nullable":true},"customerEmail":{"type":"string","nullable":true},"manageUrl":{"type":"string","nullable":true,"description":"Portal del comprador de vexorpay, donde el cliente gestiona su suscripción (tarjeta, cancelación, recibos) entrando con su email. Para un acceso directo sin código: POST /subscriptions/{id}/portal."},"sandbox":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"}}},"Balance":{"type":"object","description":"Saldo de la cuenta de cobro del creador, en centavos de USD.","required":["currency","availableCents","pendingCents","reserveCents","asOf"],"properties":{"currency":{"type":"string","enum":["usd"]},"availableCents":{"type":"integer","description":"Disponible para retirar."},"pendingCents":{"type":"integer","description":"Cobrado pero todavía no disponible."},"reserveCents":{"type":"integer","nullable":true,"description":"Retenido como reserva (null si no se informa)."},"asOf":{"type":"string","format":"date-time","description":"Momento de la lectura."}}},"Order":{"type":"object","description":"Venta: pago único, alta de suscripción (una prueba gratis registra una venta de USD 0) o renovación. Montos en centavos de USD.","required":["id","paymentLinkId","subscriptionId","checkoutId","reference","metadata","status","billingReason","currency","subtotalCents","taxCents","totalCents","feeCents","feeBreakdown","netCents","refundedCents","refundableCents","customerEmail","customerName","productName","sandbox","createdAt"],"properties":{"id":{"type":"string","description":"ID de la venta (usar en /orders/{id})."},"paymentLinkId":{"type":"string","nullable":true},"subscriptionId":{"type":"string","nullable":true,"description":"Suscripción a la que pertenece el cobro (null en un pago único)."},"checkoutId":{"type":"string","nullable":true,"description":"Sesión de checkout que generó la venta: el `checkoutId` que devuelve `POST /payment-links/{id}/checkout`."},"reference":{"type":"string","nullable":true,"description":"Referencia única de la venta: la del integrador (`/p/{slug}?reference=<valor>` o `reference` en `POST /payment-links/{id}/checkout`) o, si no la mandó, una generada por vexorpay (`VXP-…`). Las renovaciones la heredan de la suscripción."},"metadata":{"type":"object","nullable":true,"additionalProperties":true,"description":"Metadata del integrador (la del checkout por API y las `utm_*` del link), sin las claves internas de vexorpay."},"status":{"type":"string","enum":["paid","partially_refunded","refunded","pending","failed"]},"billingReason":{"type":"string","nullable":true,"description":"`one_time` (pago único), `subscription_create` (alta de suscripción) o `subscription_cycle` (renovación)."},"currency":{"type":"string","enum":["usd"],"description":"Siempre USD, aunque el comprador haya pagado en su moneda local."},"subtotalCents":{"type":"integer","description":"Precio antes de impuestos."},"taxCents":{"type":"integer","description":"Impuesto cobrado (0 si el creador se encarga de sus impuestos, la opción por defecto)."},"totalCents":{"type":"integer","description":"Lo que pagó el comprador."},"feeCents":{"type":"integer","nullable":true,"description":"Comisión de vexorpay de la venta, con el procesamiento del pago incluido (total − neto; desde 2,7 % + USD 1). 0 en una venta de USD 0; null mientras no se conozca."},"feeBreakdown":{"type":"object","nullable":true,"description":"Desglose de `feeCents` (null mientras no se conozca): la comisión general de vexorpay (`percent` % del total + `fixedCents`, con los valores reales de la venta) y los adicionales de esa venta. `vexorpay.amountCents` + la suma de `extras[].amountCents` = `feeCents`.","required":["vexorpay","extras"],"properties":{"vexorpay":{"type":"object","required":["amountCents","percent","fixedCents"],"properties":{"amountCents":{"type":"integer","description":"Comisión general de la venta."},"percent":{"type":"number","nullable":true,"description":"Porcentaje sobre el total (p. ej. 2.7; cambia con el país de la tarjeta)."},"fixedCents":{"type":"integer","description":"Parte fija (p. ej. 100 = USD 1)."}}},"extras":{"type":"array","description":"Adicionales de la venta. Pueden aparecer tipos nuevos: tratar un `type` desconocido como `other`.","items":{"type":"object","required":["type","amountCents","percent"],"properties":{"type":{"type":"string","enum":["cross_border","currency_conversion","subscription","processing","fraud_prevention","authentication","other"],"description":"`cross_border` tarjeta de fuera de la región (1,5 %), `currency_conversion` pago en otra moneda (1 %), `subscription` cobro de suscripción (0,5 %), `processing` procesamiento adicional, `fraud_prevention` prevención de fraude, `authentication` 3D Secure, `other` otros cargos."},"amountCents":{"type":"integer"},"percent":{"type":"number","nullable":true,"description":"Porcentaje sobre el total, si tiene uno publicado; null si es un monto fijo."}}}}}},"netCents":{"type":"integer","description":"Lo que recibe el creador: total menos impuesto y comisiones."},"refundedCents":{"type":"integer"},"refundableCents":{"type":"integer","description":"Lo que todavía se puede reembolsar."},"customerEmail":{"type":"string","nullable":true},"customerName":{"type":"string","nullable":true},"productName":{"type":"string","nullable":true},"sandbox":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"}}},"Customer":{"type":"object","description":"Cliente (comprador), derivado de las ventas.","required":["id","email","name","ordersCount","totalSpentCents","activeSubscriptions","firstPurchaseAt","lastPurchaseAt"],"properties":{"id":{"type":"string","description":"Identificador del cliente (email normalizado o id interno). Valor opaco: codificarlo en la URL de /customers/{id}."},"email":{"type":"string","nullable":true},"name":{"type":"string","nullable":true},"ordersCount":{"type":"integer","description":"Ventas pagadas (incluye las reembolsadas)."},"totalSpentCents":{"type":"integer","description":"Total pagado, neto de reembolsos, en centavos de USD."},"activeSubscriptions":{"type":"integer","description":"Suscripciones active, trialing o past_due."},"firstPurchaseAt":{"type":"string","format":"date-time","nullable":true},"lastPurchaseAt":{"type":"string","format":"date-time","nullable":true}}},"CustomerList":{"type":"object","required":["data","has_more","total_count","url"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Customer"}},"has_more":{"type":"boolean"},"total_count":{"type":"integer"},"url":{"type":"string"}}},"CustomerDetail":{"allOf":[{"$ref":"#/components/schemas/Customer"},{"type":"object","required":["orders","subscriptions"],"properties":{"orders":{"type":"array","items":{"$ref":"#/components/schemas/Order"}},"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/Subscription"}}}}]},"OrderList":{"type":"object","required":["data","has_more","total_count","url"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Order"}},"has_more":{"type":"boolean"},"total_count":{"type":"integer"},"url":{"type":"string"}}},"OrderRefundInput":{"type":"object","additionalProperties":false,"properties":{"amount":{"type":"number","description":"Dólares a reembolsar (hasta `refundableCents`). Default: todo lo reembolsable."}}},"PaymentLinkStats":{"type":"object","description":"Métricas de un link. Campos comunes: `paymentLinkId` y `type`. Montos en centavos de USD, netos del creador.","required":["paymentLinkId","type"],"properties":{"paymentLinkId":{"type":"string"},"type":{"type":"string","enum":["one_time","recurring"]},"salesCount":{"type":"integer","description":"one_time: ventas vigentes (sin las reembolsadas por completo, que van en `refunds`)."},"grossCents":{"type":"integer","description":"one_time: lo que pagaron los compradores (con impuesto)."},"netCents":{"type":"integer","description":"one_time: ingreso neto."},"avgTicketCents":{"type":"integer","description":"one_time: neto promedio por venta."},"customers":{"type":"integer","description":"one_time: clientes distintos (por email)."},"repeatCustomers":{"type":"integer","description":"one_time: clientes con más de una compra."},"refunds":{"type":"object","properties":{"count":{"type":"integer"},"cents":{"type":"integer"}}},"disputes":{"type":"object","properties":{"count":{"type":"integer"},"cents":{"type":"integer"}}},"topCountries":{"type":"array","items":{"type":"object","properties":{"country":{"type":"string"},"count":{"type":"integer"}}}},"weekly":{"type":"array","description":"one_time: últimas 12 semanas.","items":{"type":"object","properties":{"weekStart":{"type":"string","format":"date-time"},"netCents":{"type":"integer"},"count":{"type":"integer"}}}},"activeSubscribers":{"type":"integer","description":"recurring: suscripciones `active`."},"mrrCents":{"type":"integer","description":"recurring: ingreso mensual recurrente de las suscripciones `active` (sin las de prueba)."},"trialing":{"type":"object","description":"recurring: en período de prueba y su MRR potencial.","properties":{"count":{"type":"integer"},"mrrCents":{"type":"integer"}}},"atRisk":{"type":"object","description":"recurring: con el pago fallido (`past_due`/`unpaid`).","properties":{"count":{"type":"integer"},"mrrCents":{"type":"integer"}}},"newThisMonth":{"type":"integer"},"canceledThisMonth":{"type":"integer"},"churnRate":{"type":"number","nullable":true,"description":"recurring: bajas del mes / suscriptores al empezar el mes (0–1)."},"trialConversion":{"type":"object","properties":{"converted":{"type":"integer"},"total":{"type":"integer"}}},"netThisMonthCents":{"type":"integer"},"netTotalCents":{"type":"integer"},"ltvCents":{"type":"integer","description":"recurring: ingreso neto promedio por suscriptor."},"monthly":{"type":"array","description":"recurring: últimos 12 meses.","items":{"type":"object","properties":{"month":{"type":"string","example":"2026-10"},"netCents":{"type":"integer"}}}}}},"Payout":{"type":"object","description":"Retiro del saldo disponible al medio de retiro de la cuenta de cobro.","required":["id","amountCents","feeCents","currency","status","createdAt","arrivalDate"],"properties":{"id":{"type":"string"},"amountCents":{"type":"integer","description":"Monto del retiro en centavos de USD."},"feeCents":{"type":"integer","description":"Comisión del retiro."},"currency":{"type":"string","example":"usd"},"status":{"type":"string","description":"Estado del retiro (p. ej. `pending`, `in_transit`, `paid`, `failed`, `canceled`).","example":"paid"},"createdAt":{"type":"string","format":"date-time"},"arrivalDate":{"type":"string","format":"date-time","nullable":true,"description":"Fecha estimada (o efectiva) de acreditación."}}},"Event":{"type":"object","description":"Evento de negocio con id determinístico (`evt_...`) idéntico al que se firma en los webhooks salientes.","required":["id","type","created","sandbox","data"],"properties":{"id":{"type":"string","description":"ID determinístico del evento (mismo que el webhook saliente)."},"type":{"type":"string","enum":["payment.completed","payment.refunded","subscription.created","subscription.updated","subscription.canceled","dispute.created","dispute.updated"]},"created":{"type":"string","format":"date-time"},"sandbox":{"type":"boolean"},"data":{"type":"object","required":["object"],"properties":{"object":{"description":"El recurso del evento, con la misma forma que en la API: Order en `payment.*`, Subscription en `subscription.*` y Dispute en `dispute.*`.","oneOf":[{"$ref":"#/components/schemas/Order"},{"$ref":"#/components/schemas/Subscription"},{"$ref":"#/components/schemas/Dispute"}]}}}}},"PaymentLinkList":{"type":"object","required":["data","has_more","total_count","url"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PaymentLink"}},"has_more":{"type":"boolean"},"total_count":{"type":"integer"},"url":{"type":"string","description":"URL del request (utilizable para paginar con limit)."}}},"SubscriptionList":{"type":"object","required":["data","has_more","total_count","url"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Subscription"}},"has_more":{"type":"boolean"},"total_count":{"type":"integer"},"url":{"type":"string"}}},"PayoutList":{"type":"object","required":["data","has_more","total_count","url"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Payout"}},"has_more":{"type":"boolean"},"total_count":{"type":"integer"},"url":{"type":"string"}}},"EventList":{"type":"object","required":["data","has_more","total_count","url"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"has_more":{"type":"boolean"},"total_count":{"type":"integer"},"url":{"type":"string"}}},"ConnectedAccount":{"type":"object","description":"Conexión de una cuenta conectada a una app de marketplace (`AppConnection` serializada).","required":["id","connected_user_id","name","email","mode","scopes","created_at","revoked_at","sandbox"],"properties":{"id":{"type":"string","description":"ID de la conexión (`AppConnection.id`)."},"connected_user_id":{"type":"string","description":"ID del usuario conectado (el que va en `Vexor-Account`)."},"name":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"mode":{"type":"string","enum":["live","test"]},"scopes":{"type":"array","items":{"type":"string"},"description":"Scopes otorgados al autorizar la conexión."},"created_at":{"type":"string","format":"date-time"},"revoked_at":{"type":"string","format":"date-time","nullable":true},"sandbox":{"type":"boolean","description":"true si la conexión es del modo de prueba."}}},"ConnectedAccountList":{"type":"object","required":["data","has_more","total_count","url"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ConnectedAccount"}},"has_more":{"type":"boolean"},"total_count":{"type":"integer"},"url":{"type":"string"}}}}}}