Vio eSIM
API REST · v1

Referencia de la API de Vio eSIM

Dos interfaces REST sobre el mismo backend: la API orientada al cliente que impulsa la aplicación web y nuestras apps de iOS/Android, y una API de Socios independiente, autenticada por clave API, para revender Vio eSIM de forma programática.

API Móvil / Web
https://vioesim.com/api/v1
API de Socios
https://vioesim.com/api/public/v1

Autenticación de la app móvil

La app web y las apps nativas comparten un mismo sistema de sesión — una app nativa simplemente lleva el token consigo en lugar de depender de una cookie.

Regístrate o inicia sesión. Ambos endpoints devuelven un campo token junto al objeto de usuario (y una cabecera Set-Cookie que usa la app web en su lugar). Guarda token en el Llavero (iOS) o en el almacenamiento respaldado por Keystore (Android).

Envíalo de vuelta en cada solicitud:

Authorization: Bearer <token>

Duración del token: 30 días desde su emisión, o hasta que se llame a /api/v1/auth/logout. No hay paso de token de actualización — un token próximo a expirar simplemente se reemplaza pidiendo al usuario que inicie sesión de nuevo.

Una sesión en una cuenta suspendida, bloqueada o eliminada deja de funcionar de inmediato — se comprueba en el servidor en cada solicitud, aunque el token en sí no haya expirado.

Autenticación de la API de Socios

Para empresas externas que revenden Vio eSIM a través de su propio sitio o app. Un socio es una cuenta normal de Vio eSIM: genera una clave desde Panel → Claves API (se muestra una sola vez — guárdala, no podrá volver a mostrarse), y luego envíala en cada solicitud:

Authorization: Bearer vio_live_sk_...
# or
X-API-Key: vio_live_sk_...

Generar una nueva clave invalida inmediatamente la anterior.

Modelo de facturación: Los pedidos de la API de Socios se cobran de forma sincrónica contra el saldo prepago de la cuenta — sin redirección de pago, ya que es una llamada servidor a servidor. Recarga desde el panel (tarjeta/cripto) antes de hacer pedidos; POST /orders devuelve 402 si el saldo es insuficiente.

Inicio rápido — API de Socios

Explora el catálogo y compra una eSIM en dos llamadas:

curl https://vioesim.com/api/public/v1/destinations \
  -H "Authorization: Bearer vio_live_sk_..."

curl -X POST https://vioesim.com/api/public/v1/orders \
  -H "Authorization: Bearer vio_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "planId": "cus...", "quantity": 1 }'

Errores y límites de frecuencia

La API Móvil/Web devuelve { "error": "mensaje" } (mensajes actualmente en turco). La API de Socios devuelve una forma estructurada para que puedas ramificar según code:

{ "error": { "code": "INSUFFICIENT_BALANCE", "message": "Insufficient wallet balance." } }
EstadoSignificado
401Token o clave API faltante/inválido/expirado
402Solo API de Socios — saldo del monedero demasiado bajo
404Recurso no encontrado, o no pertenece al llamador
409Estado conflictivo (p. ej. eliminar una cuenta con saldo distinto de cero)
429Límite de frecuencia alcanzado — ver cabecera Retry-After (segundos)
502Pago capturado pero falló el aprovisionamiento de la eSIM — no reintentes el cobro

Límites de frecuencia (API de Socios): 120 solicitudes/min por clave en lecturas, 30 solicitudes/min en POST /orders.

API Móvil / Web

Autenticación

POST/api/v1/auth/registerSin autenticación

Registro

Crea una cuenta y una sesión activa.

Request body
{
  "firstName": "Ada",
  "lastName": "Lovelace",
  "email": "[email protected]",
  "password": "min 8 chars"
}
200 OK
{
  "success": true,
  "user": {
    "id", "firstName", "lastName", "fullName", "email",
    "emailVerified", "walletBalance", "currency", "locale", "createdAt"
  },
  "token": "..."
}
POST/api/v1/auth/loginSin autenticación

Inicio de sesión

Cuerpo: { email, password }. Misma forma de respuesta que Registro. 401 con credenciales incorrectas, 403 si la cuenta está suspendida.

POST/api/v1/auth/logoutToken de sesión

Cerrar sesión

Sin cuerpo. Elimina la sesión en el servidor — llama esto en un cierre de sesión real, no solo "olvidar el token localmente", para que un token robado no pueda seguir funcionando.

GET/api/v1/auth/meToken de sesión · opcional

Usuario actual

Devuelve { "user": null } (nunca un error) al estar desconectado — úsalo al iniciar la app para decidir si mostrar la pantalla de inicio de sesión.

POST/api/v1/auth/forgot-passwordSin autenticación

Olvidé mi contraseña

Cuerpo: { email, locale }. Siempre devuelve { success: true } exista o no la dirección, para que no pueda usarse para enumerar cuentas. Envía un correo con enlace/token de restablecimiento (expira en 1 hora).

POST/api/v1/auth/reset-passwordSin autenticación

Restablecer contraseña

Cuerpo: { token, newPassword }. Un GET con ?token= comprueba la validez antes de mostrar el formulario ({ "valid": true|false }).

POST/api/v1/auth/change-passwordToken de sesión

Cambiar contraseña

Cuerpo: { currentPassword, newPassword }. currentPassword es obligatorio salvo que la cuenta aún no tenga contraseña (p. ej. inicio de sesión social).

Cuenta

PATCH/api/v1/user/profileToken de sesión

Actualizar perfil

Envía solo los campos que quieras cambiar.

FieldNotes
firstName, lastNameopcional · texto
preferredCurrencyopcional · uno de USD EUR GBP TRY
localeopcional · código de 2 letras, p. ej. es
200 OK
{ "success": true, "user": { ...same shape as /auth/me } }
POST/api/v1/user/avatarToken de sesión

Actualizar avatar

Cuerpo: { "avatarUrl": "data:image/..." } — un URI de datos en base64, máx. ~1,5 MB.

POST/api/v1/user/api-key/generateToken de sesión

Generar clave API

Emite una nueva clave API de Socios para esta cuenta, reemplazando cualquier anterior. La clave sin procesar se muestra una sola vez, en esta respuesta — después solo se puede recuperar su prefijo.

200 OK
{
  "success": true,
  "key": "vio_live_sk_...",
  "apiKeyPrefix": "vio_live_sk_ab12…9f8e",
  "apiKeyGeneratedAt": "2026-08-22T17:00:00.781Z"
}
DELETE/api/v1/user/accountToken de sesión

Eliminar cuenta

Requerido para la revisión de App Store (las apps con creación de cuentas deben ofrecer eliminación dentro de la app). Anonimiza la cuenta (correo codificado y liberado para un nuevo registro, nombre/avatar/contraseña borrados) en lugar de eliminar filas de forma permanente, para que los pedidos anteriores permanezcan en los registros fiscales/contables. Destruye todas las sesiones y dispositivos push registrados del usuario.

Devuelve 409 si walletBalance > 0 — la app debe indicar al usuario que retire fondos o contacte con soporte primero, en lugar de perderlos silenciosamente.

Catálogo

GET/api/v1/destinationsSin autenticación

Listar destinos

Países y regiones con planes activos, con precio y formateados para UI (esto es lo que llama la propia tienda). Opcional ?filter=popular|regional|global|<texto de búsqueda>.

GET/api/v1/destinations/:codeSin autenticación

Detalle de destino

Detalle completo (descripción, planes) de un país por código ISO, p. ej. /api/v1/destinations/jp.

Compras

POST/api/v1/checkoutToken de sesión

Pago

Compra un plan directamente (en lugar de depositar antes en el monedero).

FieldNotes
planId*
quantityopcional · 1–10, por defecto 1
paymentMethod*wallet | stripe | crypto
successUrl, cancelUrlopcional · para redirecciones de stripe/crypto. Usa un enlace universal/de app, no una URL web simple, para que la redirección reabra la app.
200 OK — wallet (synchronous)
{ "success": true, "orderId": "..." }
200 OK — stripe / crypto (redirect required)
{ "success": true, "orderId": "...", "url": "https://checkout.stripe.com/..." }
Abre url en un navegador integrado (SFSafariViewController / Chrome Custom Tabs), no en una WebView simple — tanto Stripe como Cryptomus esperan un contexto de navegador real.
POST/api/v1/payments/stripe/create-sessionToken de sesión

Recarga de monedero con Stripe

Recarga el monedero con tarjeta — un flujo separado del pago. Cuerpo: { amount (centavos, mín. 200), currency, successUrl, cancelUrl }. Devuelve { sessionId, url }.

POST/api/v1/payments/cryptomus/create-invoiceToken de sesión

Recarga de monedero con cripto

Cuerpo: { amount (cadena decimal, mín. $2), currency, url_return }. Devuelve el objeto de factura de Cryptomus, incluyendo url.

GET/api/v1/user/ordersToken de sesión

Listar pedidos

Todos los pedidos del llamador, del más reciente al más antiguo.

GET/api/v1/user/orders/:idToken de sesión

Detalle de pedido

Un pedido, incluyendo esimIds — obtén el detalle completo de la eSIM (QR, código de activación) mediante GET /api/v1/user/esims y haz coincidir por id.

GET/api/v1/user/esimsToken de sesión

Mis eSIM

Cada eSIM que posee el llamador: iccid, activationCode, qrCodeUrl, smdpAddress, status, dataUsage/totalVolume (MB), activatedAt, expiresAt. Esto alimenta la pantalla "Mis eSIM" y la visualización del QR.

GET/api/v1/user/wallet/transactionsToken de sesión

Historial del monedero

Historial de depósitos/retiros/compras/reembolsos del saldo del monedero mostrado en /auth/me.

Soporte

GET/api/v1/support/ticketsToken de sesión

Listar tickets

Los tickets del llamador con sus hilos de mensajes completos, del más reciente al más antiguo.

POST/api/v1/support/ticketsToken de sesión

Nuevo ticket

Cuerpo: { subject, category, message } (category opcional).

POST/api/v1/support/tickets/:id/messagesToken de sesión

Responder a un ticket

Cuerpo: { message }. Responder a un ticket RESOLVED/CLOSED lo reabre automáticamente.

Dispositivos de notificaciones push

Solo registra tokens de dispositivo — enviar realmente requiere credenciales APNs/FCM, ver el aviso abajo.

POST/api/v1/user/push-devicesToken de sesión

Registrar dispositivo

Llamar tras obtener un token APNs o FCM. Cuerpo: { platform: "IOS"|"ANDROID", pushToken, appVersion }. Hace upsert por token, así que volver a llamarlo en cada inicio de la app es seguro y recomendable (los tokens pueden rotar).

DELETE/api/v1/user/push-devicesToken de sesión

Anular registro de dispositivo

Cuerpo: { pushToken }. Llamar al cerrar sesión para que un dispositivo compartido/reiniciado deje de recibir notificaciones de otro usuario.

API de Socios

Todo lo siguiente requiere la cabecera de clave API descrita arriba — aquí no aplica ninguna cookie de sesión.

Catálogo

GET/api/public/v1/destinationsClave API

Listar destinos

Catálogo completo, en una forma orientada a socios (no la forma de UI de la tienda).

200 OK
{
  "countries": [
    {
      "code": "JP",
      "name": { "en": "Japan", "tr": "Japonya", "...": "..." },
      "flagEmoji": "🇯🇵",
      "continent": "Asia",
      "plans": [
        { "id": "cus...", "dataAmountMb": 1024, "durationDays": 7, "priceUsd": 4.25, "isTopUp": false }
      ]
    }
  ],
  "regions": [ { "code": "...", "name": {...}, "countryCount": 15, "plans": [...] } ]
}
GET/api/public/v1/destinations/:codeClave API

Detalle de destino

Un país por código ISO (p. ej. /api/public/v1/destinations/JP), misma forma de planes que arriba.

Pedidos

POST/api/public/v1/ordersClave API

Crear pedido

Compra un plan contra el saldo del monedero del llamador y aprovisiona la eSIM de forma sincrónica — la respuesta ya contiene los datos de QR/activación, listos para entregar a tu cliente final. No se necesita sondeo en el caso normal.

Request body
{ "planId": "cus...", "quantity": 1 }
201 Created
{
  "id": "...", "orderNumber": "VIO-API-...", "status": "COMPLETED", "amountUsd": 4.25,
  "esims": [
    { "id": "...", "iccid": "...", "activationCode": "...",
      "qrCodeUrl": "https://...", "smdpAddress": "...", "status": "PENDING" }
  ]
}
Modos de fallo: 402 INSUFFICIENT_BALANCE (recarga primero), 404 PLAN_NOT_FOUND, o 502 FULFILLMENT_FAILED (cobrado, pero el proveedor no pudo emitir la eSIM — se incluye el id del pedido, se notifica automáticamente a soporte; sondea GET /orders/:id en vez de volver a cobrar).
GET/api/public/v1/ordersClave API

Listar pedidos

Opcional ?limit= (por defecto 25, máx. 100). Forma resumida — usa el endpoint de detalle para datos de eSIM.

GET/api/public/v1/orders/:idClave API

Detalle de pedido

Detalle completo del pedido incluyendo los datos de QR/activación y el uso en vivo (dataUsageMb / totalVolumeMb) de cada eSIM — sondea esto para mostrar a tu cliente sus datos restantes.

Monedero

GET/api/public/v1/walletClave API

Saldo del monedero

Sondea antes de realizar un lote grande de pedidos, o recibe una alerta cuando esté bajo.

200 OK
{ "balanceUsd": 128.40, "currency": "USD" }

Lo que aún no está construido

Para que nada aquí se asuma funcional sin estarlo.

Envío de notificaciones push

El registro de tokens funciona; el envío real requiere credenciales APNs/FCM.

Precios específicos por socio

La API de Socios actualmente cobra el mismo precio de venta al público que la tienda. Un precio mayorista/negociado requeriría un cambio de esquema y una decisión de negocio.

Tokens de actualización

Las sesiones son tokens planos de larga duración (30 días), no pares de token de acceso corto + token de actualización. Suficiente por ahora; revisar más adelante para una postura de seguridad más estricta.

Pantalla de pago nativa de Stripe

La integración de Stripe usa Checkout alojado (redirección/webview). Una entrada de tarjeta nativa usaría en su lugar PaymentIntents + el SDK de Stripe.

Webhooks para socios

Los socios deben sondear GET /orders/:id para el estado; aún no hay un webhook saliente (p. ej. "eSIM activada").

Referencia de la API de Vio eSIM y documentación de la Partner API