{"openapi":"3.0.3","info":{"title":"vexorpay API","version":"1.0.0","description":"API pública de vexorpay para cobrar en USD en Argentina y otros países. Los links de pago siempre operan en dólares y se cobran con Polar, el Merchant of Record: Polar procesa el pago, calcula y factura el impuesto, y acredita la venta en el saldo de Polar del creador, que lo retira a su cuenta de cobro. 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.whop.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 de Polar."},{"name":"Subscriptions","description":"Suscripciones recurrentes cobradas por Polar."},{"name":"Balance","description":"Saldo de Polar del creador."},{"name":"Orders","description":"Ventas cobradas por Polar (Merchant of Record) y sus reembolsos."},{"name":"Customers","description":"Clientes (compradores) de Polar, con sus totales, ventas y suscripciones."},{"name":"Payouts","description":"Retiros del saldo de Polar a la cuenta de cobro 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. Idempotente con el header `Idempotency-Key`: un replay con el mismo body devuelve el link ya creado (200). El link se crea en Polar (Merchant of Record), así que el creador necesita Polar conectado (si no, 400 `account_not_connected`) y su organización lista para cobrar (si no, 409 `polar_not_ready`). `checkoutUrl` es el checkout de Polar, Polar calcula el impuesto, `image_file_id` se sube a Polar como imagen del producto y un plan recurrente de precio fijo puede valer 0 (plan gratis). Una clave `vxp_test_` no crea links en producción (400 `polar_test_key_not_supported`). 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, invalid_seats, invalid_seat_tiers, invalid_tiers_mode, unsupported_plan, account_not_connected (el creador no conectó Polar) o polar_test_key_not_supported (clave `vxp_test_` en producción).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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. polar_not_ready: la organización de Polar del creador todavía no puede cobrar (`detail.reason` explica el motivo).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"polar_provisioning_failed: Polar rechazó la creación del link (no quedó nada publicado, se puede reintentar), o unexpected_error.","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"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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. `checkoutUrl` es el checkout de Polar del link.","responses":{"200":{"description":"Detalle del link de pago.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLink"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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; el cambio se aplica en Polar. Se editan `title`, `description`, `image_file_id`, `reference`, `amount` (pago único) y, en links recurrentes, `amount` (0 = gratis), `description` y `trial_period_days` de cada plan existente (cada plan va con su `id`; sin `prices` los planes no cambian). El `type` es inmutable (400 `validation_error`). Agregar o quitar planes, o cambiar el intervalo o los asientos, cambia la estructura del link y responde 400 `polar_link_structure_change`: hay que crear un link nuevo. `status` acepta `active` o `archived` (otro valor → 400 `polar_field_not_supported`); un link archivado no se edita ni se reactiva (400 `payment_link_inactive`). El `checkoutUrl` no cambia.","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, polar_link_structure_change o polar_field_not_supported.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"404":{"description":"link_not_found: no existe o pertenece a otro usuario.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"polar_link_not_editable_yet: el link de Polar todavía se está creando (reintentá en unos segundos).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"$ref":"#/components/responses/PolarUpstreamError"}},"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`): sus productos se archivan en Polar y su `checkoutUrl` deja de funcionar; las suscripciones vigentes siguen cobrándose en Polar hasta que se cancelen. Un link ya `deleted` o `archived` responde 409 `already_processed` (no es idempotente). Si Polar falla responde 502 `polar_archive_failed` sin cambios.","responses":{"200":{"description":"Link archivado (`status: archived`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentLink"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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"},"502":{"$ref":"#/components/responses/PolarUpstreamError"}},"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, comisión de Polar ni reembolsos), sobre las ventas de Polar 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"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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 de Polar 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) y, en planes por asiento, cotiza `seats`. `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 de Polar creada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PolarCheckoutResponse"}}}},"400":{"description":"invalid_request, validation_error (p. ej. `price_id` en un link de pago único), prices_required o invalid_seats.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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":"polar_link_not_editable_yet: el link todavía se está creando en Polar. polar_reconnect_required: la conexión con Polar es anterior al checkout por API (el creador reconecta Polar desde el dashboard).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"$ref":"#/components/responses/PolarUpstreamError"}},"security":[{"bearerAuth":[]},{"oauth":["charges:write"]}]}},"/disputes":{"get":{"tags":["Disputes"],"operationId":"listDisputes","summary":"Listar disputas","description":"Lista las disputas (chargebacks) de las ventas de Polar del usuario con paginación por cursor, orden descendente por fecha de creación. Polar las sincroniza cada 10 minutos (no tiene webhook de disputas). El modo (prueba o producción) lo fija la clave.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["prevented","early_warning","needs_response","under_review","won","lost"]},"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"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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 local de la disputa."}],"get":{"tags":["Disputes"],"operationId":"getDispute","summary":"Ver una disputa","description":"Detalle de una disputa de Polar. Polar la gestiona como Merchant of Record: no recibe evidencia del creador y la única acción posible es aceptarla (`POST /disputes/{id}/accept`).","responses":{"200":{"description":"Detalle de la disputa.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Dispute"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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) de Polar del creador, del más nuevo al más viejo, con sus estadísticas: compras, total pagado (neto de reembolsos), reembolsado, suscripciones activas y última compra. Incluye a quienes compraron antes de conectar vexorpay (se importan de Polar).","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"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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 de vexorpay del cliente."}],"get":{"tags":["Customers"],"operationId":"getCustomer","summary":"Ver un cliente","description":"Devuelve un cliente con sus estadísticas, 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"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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 de Polar","description":"Lista las ventas cobradas por Polar (pagos únicos y renovaciones de suscripción), de la más nueva a la más vieja.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["paid","refunded","partially_refunded","pending","void"]}},{"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"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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 vexorpay de la venta."}],"get":{"tags":["Orders"],"operationId":"getOrder","summary":"Ver una venta de Polar","description":"Devuelve una venta cobrada por Polar.","responses":{"200":{"description":"La venta.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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 vexorpay de la venta."}],"post":{"tags":["Orders"],"operationId":"refundOrder","summary":"Reembolsar una venta de Polar","description":"Reembolsa total o parcialmente una venta `paid` o `partially_refunded`. Sale del saldo del creador en Polar; el impuesto se devuelve en proporción. Irreversible. Idempotente con `Idempotency-Key` (un replay devuelve la venta sin volver a reembolsar). Exige que la conexión de Polar tenga el permiso de reembolsos (si no, 409 `polar_reconnect_required`).","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, motivo inválido o la venta no está pagada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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":"polar_reconnect_required o idempotency_conflict.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"$ref":"#/components/responses/PolarUpstreamError"}},"security":[{"bearerAuth":[]},{"oauth":["charges:write"]}]}},"/disputes/{id}/accept":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"ID local 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. Es la única acción posible sobre una disputa: Polar la defiende y no recibe evidencia del creador. 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"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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"}}}},"409":{"description":"polar_reconnect_required: la conexión de Polar no tiene el permiso de disputas.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"$ref":"#/components/responses/PolarUpstreamError"}},"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":["active","trialing","past_due","canceled","unpaid","pending","incomplete"]},"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."},{"name":"reference","in":"query","required":false,"schema":{"type":"string"},"description":"Filtra por la referencia del integrador (coincidencia exacta)."},{"$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"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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"]}]},"patch":{"tags":["Subscriptions"],"operationId":"updateSubscriptionSeats","summary":"Cambiar la cantidad de asientos","description":"Cambia la cantidad de asientos (`seats`) de una suscripción activa con plan per-seat. Es el único campo mutable: cambiar de plan implica cancelar y volver a suscribirse. Devuelve la suscripción con `seats` y `seatTotalCents` adicionales a nivel raíz. El cambio se hace en Polar y la diferencia se factura al instante (al agregar asientos se cobra el prorrateo; al quitarlos queda crédito a favor del comprador). Sólo suscripciones `active`/`trialing` sin cancelación programada.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionSeatsUpdate"}}}},"responses":{"200":{"description":"Suscripción actualizada con los nuevos `seats` y `seatTotalCents`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionSeatsUpdateResponse"}}}},"400":{"description":"validation_error: campo desconocido o falta `seats`. invalid_seats: `seats` fuera de los límites del plan, en un plan de precio fijo o en una suscripción que no está activa o tiene la cancelación programada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"404":{"description":"subscription_not_found o price_not_found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"polar_reconnect_required: la conexión de Polar no tiene el permiso de suscripciones (el creador reconecta Polar).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"$ref":"#/components/responses/PolarUpstreamError"}},"security":[{"bearerAuth":[]},{"oauth":["subscriptions:write"]}]}},"/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`). Idempotente: cancelar una suscripción ya cancelada o con cancelación programada devuelve 200 con su estado actual. La cancelación se hace en Polar (502 `polar_update_failed` si Polar falla).","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"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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/PolarUpstreamError"}},"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":"Abrir el portal del cliente de Polar del suscriptor","description":"Crea una sesión del portal del cliente de Polar para que el suscriptor administre su suscripción (método de pago, facturas, cancelación). Devuelve la URL a la que hay que redirigir.","responses":{"200":{"description":"URL del portal del cliente de Polar.","content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"URL del portal del cliente de Polar."}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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/PolarUpstreamError"}},"security":[{"bearerAuth":[]},{"oauth":["subscriptions:write"]}]}},"/balance":{"get":{"tags":["Balance"],"operationId":"getBalance","summary":"Ver el saldo de Polar","description":"Devuelve en `polar` el saldo de Polar del creador: disponible, retenido (Polar retiene cada cobro 7 días) y retirado, con su estimado en moneda local. Sin Polar conectado responde 200 con `connected: false` (no es un error). Si Polar no responde, `polar.unavailable: true` lo informa sin fallar la respuesta.","responses":{"200":{"description":"Saldo de Polar (o `connected: false` sin Polar conectado).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Balance"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"security":[{"bearerAuth":[]},{"oauth":["read"]}]}},"/payouts":{"get":{"tags":["Payouts"],"operationId":"listPayouts","summary":"Listar retiros","description":"Lista los retiros de Polar del creador, del más nuevo al más viejo, hechos desde vexorpay o desde Polar (`source` dice cuál). Se actualizan al crearlos y cada 10 minutos.","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"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"security":[{"bearerAuth":[]},{"oauth":["read"]}]},"post":{"tags":["Payouts"],"operationId":"createPayout","summary":"Retirar el saldo de Polar","description":"Retira TODO el saldo disponible de Polar a la cuenta de cobro del creador (Polar no acepta un monto parcial; descuenta sus comisiones de retiro y Stripe convierte a la moneda de la cuenta). Mueve dinero: con OAuth exige el permiso `payouts:write`. Consultar antes `GET /payouts/estimate`. Idempotente con `Idempotency-Key`: un replay devuelve el mismo retiro sin crear otro.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{}}}}},"responses":{"201":{"description":"Retiro creado en Polar.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PolarPayout"}}}},"400":{"description":"insufficient_balance: el disponible no alcanza para un retiro; validation_error: el body tiene campos.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"403":{"description":"insufficient_scope: la conexión OAuth no tiene `payouts:write`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"polar_not_connected, polar_reconnect_required (la conexión de Polar no tiene `payouts:write`), payout_account_not_ready (la cuenta de cobro de Polar no está lista) o payout_in_progress.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"polar_payout_failed: Polar no procesó el retiro.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[]},{"oauth":["payouts:write"]}]}},"/payouts/estimate":{"get":{"tags":["Payouts"],"operationId":"estimatePayout","summary":"Estimar un retiro","description":"Lo que llegaría a la cuenta de cobro si se retira ahora todo el saldo disponible de Polar: bruto, comisiones de retiro de Polar, neto y su equivalente en moneda local con la cotización de Stripe (la misma que se aplica al retirar).","responses":{"200":{"description":"Estimado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PayoutEstimate"}}}},"400":{"description":"insufficient_balance: el disponible no alcanza para un retiro.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"409":{"description":"polar_not_connected, polar_reconnect_required o payout_account_not_ready (la cuenta de cobro de Polar todavía no está lista).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"polar_payout_failed: Polar no respondió.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"bearerAuth":[]},{"oauth":["read"]}]}},"/events":{"get":{"tags":["Events"],"operationId":"listEvents","summary":"Listar eventos de webhook","description":"Lista los eventos de negocio del usuario (ventas de Polar: payment.completed/refunded/disputed; retiros: payout.paid) 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. El objeto embebido en `data.object` tiene exactamente la misma forma que el payload del webhook.","parameters":[{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["payment.completed","payment.failed","payment.refunded","payment.disputed","dispute.resolved","payout.paid","subscription.created","subscription.payment.completed","subscription.payment.failed","subscription.canceled","subscription.trial_will_end"]},"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"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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"},"402":{"$ref":"#/components/responses/SubscriptionRequired"},"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.whop.vexorpay.com/oauth/authorize","tokenUrl":"https://www.whop.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":"Actualizar, cancelar suscripciones y abrir el portal de facturación.","disputes:write":"Aceptar disputas (darlas por perdidas).","payouts:write":"Retirar el saldo disponible de Polar a la cuenta de cobro del creador.","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 (la venta es íntegra del conectado). 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"}}}},"SubscriptionRequired":{"description":"subscription_required: la suscripción a vexorpay de la cuenta no está en prueba ni al día. Se empieza la prueba gratis de 30 días (o se renueva la suscripción) desde el dashboard.","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 o upstream_error (falla inesperada al procesar la operación en Polar u otro proveedor externo).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PolarUpstreamError":{"description":"Polar rechazó o no respondió la operación sobre un link o una suscripción de Polar: polar_update_failed, polar_archive_failed o polar_checkout_failed. No se cambió nada: se puede reintentar.","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`, `reason` (motivo de Polar) en 409 `polar_not_ready`. 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 de Polar del link."},"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","pricingType","perSeatAmountCents","minSeats","maxSeats","seatTiers","tiersMode"],"properties":{"id":{"type":"string"},"amountCents":{"type":"integer","description":"Precio del ciclo en centavos de USD. En per-seat uniforme es el precio por asiento; en base+extra es la base que cubre el primer asiento."},"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"},"pricingType":{"type":"string","enum":["flat","per_seat"]},"perSeatAmountCents":{"type":"integer","nullable":true,"description":"Precio de cada asiento extra en centavos (null si todos cuestan lo mismo)."},"minSeats":{"type":"integer"},"maxSeats":{"type":"integer","nullable":true},"seatTiers":{"type":"array","nullable":true,"items":{"$ref":"#/components/schemas/SeatTier"}},"tiersMode":{"type":"string","enum":["graduated","volume"],"nullable":true}}},"SeatTier":{"type":"object","required":["upTo"],"properties":{"upTo":{"type":"integer","nullable":true,"description":"Cantidad máxima de asientos del tramo; null = último tramo (hasta el infinito)."},"unitAmountCents":{"type":"integer","description":"Precio por unidad del tramo en centavos."},"flatAmountCents":{"type":"integer","description":"Cargo fijo del tramo en centavos."}}},"PaymentLinkCreate":{"type":"object","required":["amount"],"properties":{"amount":{"type":"number","description":"Monto en dólares (USD) para links one-time. Mínimo 4. Ignorado cuando `type` es recurring.","minimum":4,"example":25},"title":{"type":"string","description":"Título del link."},"description":{"type":"string"},"image_file_id":{"type":"string","description":"ID de un archivo GridFS completado del usuario (foto del producto). En links de Polar se sube a Polar como imagen del producto."},"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.","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"},"pricing_type":{"type":"string","enum":["flat","per_seat"]},"per_seat_amount":{"type":"number","description":"Precio en dólares de cada asiento extra (per-seat base+extra)."},"min_seats":{"type":"integer","default":1},"max_seats":{"type":"integer","nullable":true},"seat_tiers":{"type":"array","description":"Matriz de tramos de precios. Usa CENTAVOS por asiento, a diferencia de `amount`/`per_seat_amount` que van en dólares.","items":{"$ref":"#/components/schemas/SeatTier"}},"tiers_mode":{"type":"string","enum":["graduated","volume"],"nullable":true}}},"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`. Agregar o quitar planes responde 400 `polar_link_structure_change`.","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-…`). Viaja a Polar como `reference_id` y vuelve en la venta (`reference` de `GET /orders`), en sus webhooks y en `/events`."},"metadata":{"type":"object","additionalProperties":true,"description":"Metadatos de la compra que se guardan en el checkout de Polar. Sólo valores planos (string, número o booleano)."},"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."},"seats":{"type":"integer","description":"Cantidad de asientos a cotizar (solo planes per_seat). Ausente = min_seats."}}},"PolarCheckoutResponse":{"type":"object","description":"Respuesta de checkout: una sesión de checkout de Polar por llamada.","required":["provider","polarCheckoutId","reference","price_id","seats","checkoutUrl","expiresAt"],"properties":{"provider":{"type":"string","enum":["polar"]},"polarCheckoutId":{"type":"string"},"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)."},"seats":{"type":"integer","nullable":true,"description":"Asientos cotizados (solo planes por asiento)."},"checkoutUrl":{"type":"string","description":"URL del checkout de Polar a la que hay que redirigir al comprador."},"expiresAt":{"type":"string","format":"date-time","nullable":true}}},"Dispute":{"type":"object","description":"Disputa (chargeback) de una venta de Polar. La gestiona Polar como Merchant of Record: desde vexorpay sólo se puede aceptar.","required":["id","provider","paymentLinkId","reference","customerEmail","status","statusLabel","reason","reasonLabel","amountCents","currency","feeCents","evidenceDeadline","daysLeft","isOpen","canAccept","outcome","sandbox","paymentLinkUrl","createdAt","resolvedAt"],"properties":{"id":{"type":"string","description":"ID de vexorpay de la disputa (usar en /disputes/{id})."},"provider":{"type":"string","enum":["polar"]},"orderId":{"type":"string","nullable":true,"description":"La venta disputada (GET /orders/{id})."},"polarDisputeId":{"type":"string","description":"ID de la disputa en Polar."},"paymentLinkId":{"type":"string"},"reference":{"type":"string","nullable":true},"customerEmail":{"type":"string","nullable":true},"status":{"type":"string","enum":["prevented","early_warning","needs_response","under_review","won","lost"]},"statusLabel":{"type":"string","description":"Estado traducido al español."},"reason":{"type":"string","nullable":true,"description":"Motivo informado por Polar (p. ej. `fraudulent`, `product_not_received`)."},"reasonLabel":{"type":"string","description":"Motivo para mostrar (`Sin motivo` si Polar no lo 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 Polar descuenta del saldo del creador."},"evidenceDeadline":{"type":"string","format":"date-time","nullable":true,"description":"Plazo de respuesta informado por Polar (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) la disputa: abierta en `needs_response` o `early_warning`."},"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."},"paymentLinkUrl":{"type":"string","nullable":true},"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 cobrada por Polar.","required":["id","provider","paymentLinkId","paymentLinkPriceId","price","status","customerEmail","reference","currentPeriodStart","currentPeriodEnd","canceledAt","cancelAtPeriodEnd","trialStart","trialEnd","sandbox","createdAt"],"properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["polar"]},"polarSubscriptionId":{"type":"string","description":"ID de la suscripción en Polar."},"paymentLinkId":{"type":"string"},"paymentLinkPriceId":{"type":"string"},"price":{"$ref":"#/components/schemas/SubscriptionPriceSummary"},"status":{"type":"string","enum":["active","trialing","past_due","canceled","unpaid","pending","incomplete","incomplete_expired","paused"],"description":"Estado de la suscripción en Polar."},"customerEmail":{"type":"string","nullable":true},"reference":{"type":"string","nullable":true,"description":"Referencia del integrador del checkout que creó la suscripción (`/p/{slug}?reference=` o `reference` del checkout por API)."},"metadata":{"type":"object","nullable":true,"additionalProperties":true,"description":"Metadata del integrador, sin las claves internas de vexorpay."},"currentPeriodStart":{"type":"string","format":"date-time","nullable":true},"currentPeriodEnd":{"type":"string","format":"date-time","nullable":true},"canceledAt":{"type":"string","format":"date-time","nullable":true},"cancelAtPeriodEnd":{"type":"boolean"},"trialStart":{"type":"string","format":"date-time","nullable":true},"trialEnd":{"type":"string","format":"date-time","nullable":true},"sandbox":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"}}},"SubscriptionPriceSummary":{"type":"object","required":["amountCents","currency","interval","intervalCount","trialPeriodDays","seats","seatTotalCents","perSeatAmountCents"],"properties":{"amountCents":{"type":"integer"},"currency":{"type":"string","example":"usd"},"interval":{"type":"string","enum":["day","week","month","year"]},"intervalCount":{"type":"integer"},"trialPeriodDays":{"type":"integer","nullable":true},"seats":{"type":"integer","description":"Asientos comprados (1 en todo lo que no es per-seat)."},"seatTotalCents":{"type":"integer","description":"Total por ciclo con esos asientos, en centavos."},"perSeatAmountCents":{"type":"integer","nullable":true}}},"SubscriptionSeatsUpdate":{"type":"object","required":["seats"],"properties":{"seats":{"type":"integer","description":"Nueva cantidad de asientos (1-1000)."}}},"SubscriptionSeatsUpdateResponse":{"type":"object","description":"Respuesta de PATCH /subscriptions/{id}: la suscripción más `seats` y `seatTotalCents` a nivel raíz.","allOf":[{"$ref":"#/components/schemas/Subscription"},{"type":"object","required":["seats","seatTotalCents"],"properties":{"seats":{"type":"integer"},"seatTotalCents":{"type":"integer"}}}]},"Balance":{"type":"object","description":"Saldo de Polar del creador.","required":["connected","polar","sandbox","updatedAt"],"properties":{"connected":{"type":"boolean","description":"false si el creador no conectó Polar (igual a `polar.connected`)."},"polar":{"$ref":"#/components/schemas/PolarBalance"},"sandbox":{"type":"boolean","description":"true si la clave es del modo de prueba."},"updatedAt":{"type":"string","format":"date-time"}}},"PolarBalance":{"type":"object","description":"Saldo de Polar. `connected: false` si el creador no conectó Polar; `needsReconnect: true` si su conexión es anterior al permiso de lectura del saldo (reconectar Polar desde el dashboard); `unavailable: true` si Polar no respondió.","required":["connected"],"properties":{"connected":{"type":"boolean"},"needsReconnect":{"type":"boolean"},"unavailable":{"type":"boolean"},"currency":{"type":"string"},"balanceCents":{"type":"integer","description":"Saldo total en Polar (disponible + retenido)."},"availableCents":{"type":"integer","description":"Disponible para retirar."},"pendingCents":{"type":"integer","description":"Retenido por Polar (7 días desde cada cobro)."},"nextReleaseAt":{"type":"string","format":"date-time","nullable":true,"description":"Próxima liberación de saldo retenido."},"nextReleaseCents":{"type":"integer","description":"Monto que se libera en `nextReleaseAt`."},"paidOutCents":{"type":"integer","description":"Suma de los retiros hechos o en curso."},"payoutAccountReady":{"type":"boolean","description":"false si el creador todavía no configuró en Polar la cuenta donde recibe los retiros."},"financeUrl":{"type":"string","description":"Página de finanzas de la organización en Polar, donde se retira."},"localEstimate":{"$ref":"#/components/schemas/PolarLocalEstimate"}}},"PolarLocalEstimate":{"type":"object","nullable":true,"description":"El saldo de Polar en la moneda local del creador, con la cotización de Stripe (la misma que aplica Stripe cuando el creador retira desde Polar). `null` si la moneda de la cuenta de cobro es USD, si el creador no tiene país ni cuenta de cobro, o si no hay cotización disponible. Montos en la unidad menor de `currency` (centavos; unidades en monedas sin decimales como CLP).","required":["currency","currencySource","rate","fxFeeRate","asOf","source","available","availableBasis","pending","paidOut","paidOutExact"],"properties":{"currency":{"type":"string","description":"ISO 4217 en minúsculas (ars, clp, cop, pen, uyu…)."},"currencySource":{"type":"string","enum":["payout_account","country"],"description":"`payout_account`: moneda de la cuenta de cobro de Polar. `country`: todavía sin cuenta de cobro, moneda del país del creador."},"rate":{"type":"number","description":"Unidades de moneda local por 1 USD, con el fee de conversión de Stripe incluido."},"fxFeeRate":{"type":"number","description":"Fee de conversión de Stripe incluido en `rate` (0.01 = 1 %)."},"asOf":{"type":"string","format":"date-time","description":"Hora de la cotización (se renueva cada 15 minutos)."},"source":{"type":"string","enum":["stripe_fx_quote"]},"available":{"type":"integer","description":"Estimado de lo que llega al retirar el disponible."},"availableBasis":{"type":"string","enum":["payout_estimate","gross"],"description":"`payout_estimate`: ya sin las comisiones de retiro de Polar. `gross`: sobre el saldo bruto."},"pending":{"type":"integer","description":"Estimado del saldo retenido."},"paidOut":{"type":"integer","description":"Lo ya retirado, con el monto exacto que acreditó Stripe en cada retiro."},"paidOutExact":{"type":"boolean","description":"false si algún retiro se hizo en otra moneda (no se suma)."}}},"Order":{"type":"object","description":"Venta cobrada por Polar (Merchant of Record): pago único o renovación de suscripción.","required":["id","provider","polarOrderId","paymentLinkId","polarSubscriptionId","polarCheckoutId","reference","metadata","status","billingReason","currency","subtotalCents","taxCents","totalCents","netCents","refundedCents","refundableCents","customerEmail","customerName","productName","seats","sandbox","createdAt"],"properties":{"id":{"type":"string","description":"ID de vexorpay (usar en /orders/{id})."},"provider":{"type":"string","enum":["polar"]},"polarOrderId":{"type":"string"},"paymentLinkId":{"type":"string","nullable":true,"description":"null si el producto de Polar no es de un link de vexorpay."},"polarSubscriptionId":{"type":"string","nullable":true,"description":"Suscripción de Polar si es una renovación."},"polarCheckoutId":{"type":"string","nullable":true,"description":"Checkout de Polar que generó la venta: el `polarCheckoutId` 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. null sólo en ventas anteriores al 2026-10-30."},"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","refunded","partially_refunded","pending","void"]},"billingReason":{"type":"string","nullable":true,"description":"purchase, subscription_create, subscription_cycle…"},"currency":{"type":"string"},"subtotalCents":{"type":"integer","description":"Antes de descuentos. Con impuesto incluido (UE) el impuesto está adentro: usar `netCents` para el monto sin impuesto."},"taxCents":{"type":"integer"},"totalCents":{"type":"integer","description":"Lo que pagó el comprador."},"netCents":{"type":"integer","description":"Monto sin impuesto (después de descuentos). La comisión de Polar se descuenta aparte, en tu saldo de Polar."},"refundedCents":{"type":"integer"},"refundableCents":{"type":"integer","description":"Lo que todavía se puede reembolsar (sin impuesto)."},"customerEmail":{"type":"string","nullable":true},"customerName":{"type":"string","nullable":true},"productName":{"type":"string","nullable":true},"seats":{"type":"integer","nullable":true},"sandbox":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"}}},"Customer":{"type":"object","required":["id","provider","polarCustomerId","email","name","country","externalId","stats","sandbox","createdAt"],"properties":{"id":{"type":"string","description":"ID de vexorpay del cliente."},"provider":{"type":"string","enum":["polar"]},"polarCustomerId":{"type":"string","description":"ID del cliente en Polar."},"email":{"type":"string","nullable":true},"name":{"type":"string","nullable":true},"country":{"type":"string","nullable":true,"description":"País de facturación (ISO 3166-1 alfa-2)."},"externalId":{"type":"string","nullable":true,"description":"ID externo del cliente en Polar, si se cargó."},"stats":{"type":"object","required":["ordersCount","totalSpentCents","refundedCents","currency","activeSubscriptions","lastOrderAt"],"properties":{"ordersCount":{"type":"integer","description":"Ventas pagadas (incluye las reembolsadas)."},"totalSpentCents":{"type":"integer","description":"Total pagado, neto de reembolsos, en centavos."},"refundedCents":{"type":"integer"},"currency":{"type":"string","enum":["usd"]},"activeSubscriptions":{"type":"integer","description":"Suscripciones active, trialing o past_due."},"lastOrderAt":{"type":"string","format":"date-time","nullable":true}}},"sandbox":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time","description":"Alta del cliente en Polar."}}},"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, sin impuesto. Default: todo lo reembolsable."},"reason":{"type":"string","enum":["customer_request","duplicate","fraudulent","service_disruption","satisfaction_guarantee","other"],"description":"Default: customer_request."},"comment":{"type":"string","description":"Nota interna (hasta 500 caracteres)."}}},"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"}}}}}},"PolarPayout":{"type":"object","description":"Retiro de Polar a la cuenta de cobro del creador.","required":["id","provider","polarPayoutId","amountCents","feesCents","grossCents","currency","status","accountCurrency","accountAmount","source","sandbox","createdAt","paidAt"],"properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["polar"]},"polarPayoutId":{"type":"string"},"amountCents":{"type":"integer","description":"Neto transferido a la cuenta de cobro (USD)."},"feesCents":{"type":"integer","description":"Comisiones de retiro de Polar."},"grossCents":{"type":"integer","description":"Saldo retirado antes de comisiones."},"currency":{"type":"string","example":"usd"},"status":{"type":"string","enum":["pending","held","in_transit","paid","failed","canceled"]},"accountCurrency":{"type":"string","nullable":true,"description":"Moneda de la cuenta de cobro (ars, clp…)."},"accountAmount":{"type":"integer","nullable":true,"description":"Lo que Stripe acreditó en la cuenta de cobro, en la unidad menor de `accountCurrency`."},"source":{"type":"string","enum":["vexorpay","polar"],"description":"Dónde se inició: desde vexorpay (dashboard o API) o desde Polar."},"sandbox":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"paidAt":{"type":"string","format":"date-time","nullable":true}}},"PayoutEstimate":{"type":"object","required":["currency","grossCents","feesCents","netCents","localEstimate"],"properties":{"currency":{"type":"string","example":"usd"},"grossCents":{"type":"integer"},"feesCents":{"type":"integer","description":"Comisiones de retiro de Polar."},"netCents":{"type":"integer","description":"Lo que llega a la cuenta de cobro, en USD."},"localEstimate":{"type":"object","nullable":true,"required":["currency","amount","rate","asOf","source"],"properties":{"currency":{"type":"string"},"amount":{"type":"integer","description":"Neto en la unidad menor de la moneda local."},"rate":{"type":"number","description":"Moneda local por 1 USD, con el fee de conversión de Stripe incluido."},"asOf":{"type":"string","format":"date-time"},"source":{"type":"string","enum":["stripe_fx_quote"]}}}}},"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","payment.disputed","payout.paid"]},"created":{"type":"string","format":"date-time"},"sandbox":{"type":"boolean"},"data":{"type":"object","required":["object"],"properties":{"object":{"$ref":"#/components/schemas/EventObject"}}}}},"EventObject":{"type":"object","description":"Payload igual en forma al del webhook saliente.","properties":{"charge":{"$ref":"#/components/schemas/EventCharge"},"payment_link":{"type":"object","required":["id","slug","title","reference"],"properties":{"id":{"type":"string","nullable":true},"slug":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"reference":{"type":"string","nullable":true,"description":"Referencia del link (no la de la venta)."}}},"payout":{"type":"object","description":"Presente en eventos `payout.paid`: el retiro de Polar.","required":["id","provider","polarPayoutId","amountCents","feesCents","currency","status","sandbox","createdAt","paidAt"],"properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["polar"]},"polarPayoutId":{"type":"string"},"amountCents":{"type":"integer"},"feesCents":{"type":"integer"},"currency":{"type":"string"},"status":{"type":"string"},"accountCurrency":{"type":"string","nullable":true},"accountAmount":{"type":"integer","nullable":true},"source":{"type":"string","enum":["vexorpay","polar"]},"sandbox":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"paidAt":{"type":"string","format":"date-time","nullable":true}}}}},"EventCharge":{"type":"object","description":"La venta de Polar del evento (`id` es el de `GET /orders/{id}`).","required":["id","provider","reference","amountCents","currency","status","sandbox","refundedCents","paidAt"],"properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["polar"]},"reference":{"type":"string","nullable":true,"description":"Referencia del integrador para ESTA venta (la del link va en `payment_link.reference`)."},"metadata":{"type":"object","nullable":true,"additionalProperties":true,"description":"Metadata del integrador de la venta."},"polarOrderId":{"type":"string"},"polarCheckoutId":{"type":"string","nullable":true},"amountCents":{"type":"integer","description":"Total pagado por el comprador, en centavos."},"currency":{"type":"string"},"status":{"type":"string","enum":["paid","refunded"]},"sandbox":{"type":"boolean"},"refundedCents":{"type":"integer"},"paidAt":{"type":"string","format":"date-time","nullable":true}}},"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/PolarPayout"}},"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"}}}}}}