CBus API for Developers v1

Bienvenido a la documentación oficial de la CBus API for Developers v1. CBus es una plataforma líder en la gestión de flotas y venta de boletos para empresas de autotransporte.

Esta API RESTful proporciona a los desarrolladores un conjunto de endpoints para interactuar directamente con el sistema de ventas de CBus. El objetivo es permitir la integración de sistemas de terceros, agencias de viaje en línea (OTAs) y aplicaciones personalizadas para:

Sigue esta guía para entender el flujo de autenticación, los entornos disponibles y cada uno de los endpoints.

Autenticación

Todos los endpoints requieren autenticación mediante una API Key, tanto en Sandbox como en Producción. La API Key se obtiene contactando al equipo de CBus y está asociada a la instancia del transportista.

Puedes enviar la API Key de cualquiera de estas tres formas (elige una):

Opción 1 — Header Authorization: Bearer (recomendado)

Authorization: Bearer TU_API_KEY
Accept: application/json

Opción 2 — Header X-API-Key

X-API-Key: TU_API_KEY
Accept: application/json

Opción 3 — Query parameter api_key

GET /api/dev/v1/origenes?api_key=TU_API_KEY

Modo Sandbox vs Modo Producción

Cada API Key tiene un flag is_production. Las keys en modo producción tienen una restricción adicional: el endpoint /confirmar exige que la key tenga configuradas restricciones de IP o dominio permitido en el panel de CBus. Sin esas restricciones, /confirmar responderá con 403. Las keys en modo sandbox no aplican esta restricción.

Errores de autenticación

CódigoMensajeCausa
401 Unauthorized No se envió API Key o la key no existe.
403 IP address is not allowed. La IP del cliente no está en la lista blanca de la key.
403 Origin is not allowed. El header Origin de la solicitud no está permitido.
429 Rate limit exceeded. Se superó el límite de solicitudes por minuto del plan.

Entorno Sandbox

Todas las URL de los endpoints listados a continuación deben usar la siguiente URL base correspondiente al entorno de pruebas:

Flujo General de Venta

El proceso de reserva y confirmación de un boleto sigue un flujo específico de varios pasos. No puedes confirmar un boleto sin antes haberlo seleccionado y reservado.

  1. Búsqueda:
    • GET /api/dev/v1/origenes -> Obtiene la lista de todas las ciudades de origen.
    • GET /api/dev/v1/destinos -> Obtiene los destinos disponibles desde un origen.
    • GET /api/dev/v1/horarios -> Obtiene las corridas y horarios para una ruta y fecha.
  2. Selección:
    • POST /api/dev/v1/asientos -> Muestra el mapa de asientos disponibles para un horario.
    • POST /api/dev/v1/seleccionar-asiento -> Aparta temporalmente un asiento. Devuelve una clave única.
  3. Reserva:
    • POST /api/dev/v1/reservar -> Agrupa todas las claves de asientos (ida y regreso) bajo los datos de un cliente. Devuelve una clave_global.
  4. Confirmación:
    • POST /api/dev/v1/confirmar -> Finaliza la compra usando la clave_global y marca los boletos como "pagados".
  5. (Opcional) Cancelación:
    • POST /api/dev/v1/deseleccionar-asiento -> Libera un asiento que fue apartado temporalmente.
    • POST /api/dev/v1/boletos/cancelar -> Cancela un boleto ya pagado.
  6. (Opcional) Facturación CFDI 4.0:
    • POST /api/dev/v1/facturacion/emitir -> Timbra una factura para los boletos de una clave_global.
    • POST /api/dev/v1/facturacion/cancelar -> Marca una factura como cancelada localmente.
  7. (Opcional) Paquetería:
    • POST /api/dev/v1/envios/preregistrar -> Pre-registra un envío.
    • GET /api/dev/v1/envios/rastreo -> Consulta el estado y seguimiento de un envío por clave.

Planes de API y Límites de Tasa (Rate Limiting)

El acceso a la API está sujeto a límites de tasa (Rate Limiting) que dependen del plan de API adquirido para la instancia de CBus WEB. La Api_key asociada a la instancia de la empresa de transporte tendrá el límite de solicitudes por minuto (x-ratelimit-limit) correspondiente a su plan.

Plan de API Límite por Minuto (x-ratelimit-limit) Costo Mensual (USD)
Plan Básico (Default) 8 Incluido en la instancia
API Plan 1 15 $10.00
API Plan 2 30 $18.00
API Plan 3 90 $30.00

Si se excede el límite correspondiente al plan de la instancia, la API comenzará a responder con un código de error 429 Too Many Requests. Para adquirir o cambiar de plan, es necesario contactar al equipo de ventas de CBus.

Cuotas de Uso (Quotas)

Además de los límites de tasa por minuto, el uso de la API está sujeto a una **cuota general** basada en el **número total de claves/folios emitidos** (incluyendo apartados, cancelados, reservados y confirmados/pagados) permitidas por el plan de suscripción de la empresa de transporte con CBus.

Cada intento de seleccion de asiento emite una clave/folio

Si se excede la cuota total de reservaciones activas permitida por el plan, la API **rechazará cualquier nueva conexión o solicitud** que intente crear o confirmar una reservación, independientemente de los límites de tasa por minuto.

Es responsabilidad del cliente (la empresa de transporte) y del desarrollador estar al tanto de los límites específicos de su plan contratado.

Responsabilidades del Desarrollador

Importante: La API de CBus NO se encarga de las siguientes acciones:
  • Crear (imagen/pdf) el boleto (comprobante o confirmación de compra) al cliente final.
  • Notificar al cliente final sobre el estado de su reservación o compra (ej. por email, SMS, WhatsApp).
  • Enviar notificaciones activas (push, email, etc.) a la empresa de transporte sobre nuevas ventas o reservaciones confirmadas.

Sin embargo, toda reservación creada o confirmada a través de la API queda registrada en el sistema administrativo de CBus y puede ser consultada por el personal de la empresa de transporte a través de los reportes y módulos correspondientes.

Es responsabilidad del desarrollador que integra esta API implementar la lógica necesaria para:

  • Generar y enviar el comprobante de compra (boleto) al cliente final.
  • Establecer los mecanismos de notificación al cliente que considere necesarios.

GET /api/dev/v1/origenes

Obtiene una lista de todas las escalas (orígenes) disponibles.

Parámetros de Query

Ninguno.

Respuesta Exitosa (200 OK)

[
    {
        "id": 5,
        "nombre": "CDMX",
        "lat": null,
        "lng": null,
        "abordajes": []
    },
    {
        "id": 1,
        "nombre": "GPE",
        "lat": null,
        "lng": null,
        "abordajes": []
    },
    {
        "id": 3,
        "nombre": "OAXACA",
        "lat": null,
        "lng": null,
        "abordajes": []
    },
    {
        "id": 4,
        "nombre": "PUEBLA",
        "lat": null,
        "lng": null,
        "abordajes": []
    },
    {
        "id": 2,
        "nombre": "TEPIC",
        "lat": null,
        "lng": null,
        "abordajes": []
    }
]

GET /api/dev/v1/destinos

Obtiene los destinos disponibles basado en un nombre de origen.

Parámetros de Query

Parámetro Tipo Requerido Descripción
q string Nombre exacto del origen. Ej: DMX.

Respuesta Exitosa (200 OK)

[
    {
        "id": 3,
        "nombre": "OAXACA",
        "lat": null,
        "lng": null,
        "abordajes": []
    },
    {
        "id": 4,
        "nombre": "PUEBLA",
        "lat": null,
        "lng": null,
        "abordajes": []
    }
]

GET /api/dev/v1/horarios

Obtiene los horarios disponibles para una ruta y fechas específicas.

Parámetros de Query

Parámetro Tipo Requerido Descripción
origen string Nombre de la escala de origen (Ej: CDMX).
destino string Nombre de la escala de destino (Ej: OAXACA).
fecha_ida date Fecha de salida (Formato: YYYY-MM-DD).
fecha_regreso date No Fecha de regreso (Formato: YYYY-MM-DD).

Respuesta Exitosa (200 OK)

{
    "ida": [
        {
            "id": 3,
            "corrida_id": 2,
            "ab_ba": "ba",
            "ab": 1,
            "hora_salida": "14:00:00",
            "fcomp": 0,
            "fecha": "2025-10-30",
            "minutos_trayecto": 480,
            "activo": 1,
            "aplicar_venta_linea": 1,
            "corrida": {
                "id": 2,
                "nombre": "Oaxaca-CDMX",
                "mostrar_pv": 1,
                "aplicar_en_reservaciones": 1,
                "aplicar_venta_linea": 1,
                "extraordinaria": 0,
                "fechas_deshabilitadas": [],
                "activo": 1
            },
            "origen": {
                "id": 5,
                "nombre": "CDMX",
                "hora": "2:00 pm"
            },
            "destino": {
                "id": 3,
                "nombre": "OAXACA",
                "hora": "10:00 pm"
            },
            "disponibilidad": {
                "asientos_disponibles": 20,
                "asientos_ocupados": 0,
                "total_asientos": 20,
                "ultima_actualizacion": "2025-10-29T00:00:00.000000Z"
            }
        }
    ],
    "regreso": [
        {
            "id": 3,
            "corrida_id": 2,
            "ab_ba": "ab",
            "ab": 1,
            "hora_salida": "14:00:00",
            "fcomp": 0,
            "fecha": "2025-11-01",
            "minutos_trayecto": 480,
            "...": "..."
        }
    ]
}

GET /api/dev/v1/sucursales

Obtiene el listado completo de sucursales con su información detallada.

Parámetros de Query

Ninguno.

Respuesta Exitosa (200 OK)

{
    "success": true,
    "data": [
        {
            "id": 1,
            "nombre": "Terminal Central",
            "direccion": "Av. Principal 123",
            "estado": "Jalisco",
            "colonia": "Centro",
            "localidad": "Guadalajara",
            "municipio": "Guadalajara",
            "pais": "MX",
            "codigo_postal": "44100",
            "telefono_1": "3312345678",
            "telefono_2": null,
            "telefono_3": null,
            "lat": "20.659698",
            "lng": "-103.349609",
            "descripcion": "Terminal principal de autobuses",
            "datos_ticket_id": null,
            "pais_nombre": "México"
        }
    ],
    "total": 1
}

POST /api/dev/v1/asientos

Obtiene el mapa de asientos (layout) de un autobús para un horario específico, indicando cuáles están disponibles, ocupados o bloqueados.

Cuerpo de la Solicitud (JSON)

Parámetro Tipo Requerido Descripción
ab integer Dirección del viaje (Usar 1 o 0).
corrida integer ID de la corrida (obtenido de /horarios).
origen integer ID de la escala de origen.
destino integer ID de la escala de destino.
horario integer ID del horario (obtenido de /horarios).
redondo integer 0 para viaje sencillo.
fecha date Fecha del viaje (Formato: YYYY-MM-DD).

Ejemplo de Solicitud

{
    "ab": 1,
    "corrida": 2,
    "origen": 5,
    "destino": 3,
    "horario": 3,
    "redondo": 0,
    "fecha": "2025-10-31"
}

Respuesta Exitosa (200 OK)

{
    "escalas": {
        "origen": "CDMX",
        "destino": "OAXACA"
    },
    "precio": [ ... ],
    "hora": {
        "salida": "2:00 pm",
        "llegada": "10:00 pm"
    },
    "asientos": [
        {
            "id": 21,
            "numero": 1,
            "corrida_id": 2,
            "categoria": null,
            "categoria_abreviacion": null,
            "forma_calculo_valor": null,
            "valor": null,
            "reservaciones": [],
            "disponible": true,
            "fcomp": 0,
            "horario_id": 3,
            "origen_id": 5,
            "destino_id": 3,
            "ab": false,
            "fecha_reservacion": "2025-10-31",
            "prefix": "000"
        },
        {
            "id": 22,
            "numero": 2,
            "corrida_id": 2,
            "..." : "..."
        }
    ],
    "total_asientos": 20,
    "abordajes": [],
    "fecha_reservacion": "31-10-2025",
    "fcomp": 0
}

Nota Importante sobre la Respuesta

La respuesta de este endpoint es crucial. Cada objeto dentro del array asientos contiene los IDs exactos que necesitarás para el siguiente paso (/api/dev/v1/seleccionar-asiento).

Debes capturar y utilizar los siguientes valores del asiento que elija el usuario:

GET /api/dev/v1/render

Obtiene una representación visual (HTML) del autobús con la disposición de sus asientos y su estado de disponibilidad en tiempo real.

Este endpoint es ideal para ser mostrado en un <iframe> dentro de la aplicación del socio comercial.

Parámetros de Consulta (Query Params)

Parámetro Tipo Requerido Descripción
api_key string Tu clave de API (necesaria si se usa en iframe sin headers).
corrida int ID de la corrida.
horario int ID del horario.
origen int ID de la escala de origen.
destino int ID de la escala de destino.
fecha string Fecha del viaje (YYYY-MM-DD).

Ejemplo de Uso (Iframe)

<iframe src="https://cbus.com.mx/api/dev/v1/render?api_key=TU_API_KEY&corrida=2&horario=3&origen=5&destino=3&fecha=2025-10-31" width="100%" height="600" frameborder="0"></iframe>

Respuesta

Devuelve un documento HTML completo con estilos embebidos que renderiza el mapa del autobús.

POST /api/dev/v1/seleccionar-asiento

Aparta temporalmente un asiento específico. Este asiento se bloqueará por un tiempo limitado (ej. 10 minutos) y se generará una clave única para esta selección.

Cuerpo de la Solicitud (JSON)

Parámetro Tipo Requerido Descripción
corrida string ID de la corrida (obtenido de /asientos).
origen string ID de la escala de origen (obtenido de /asientos).
destino string ID de la escala de destino (obtenido de /asientos).
horario string ID del horario (obtenido de /asientos).
asiento string ID del asiento a seleccionar (obtenido de /asientos).
tipo_persona string adulto o nino.
ab integer Dirección del viaje (1 o 0). Importante: Debes usar el valor ab (true/false o 1/0) que se obtuvo del objeto asiento en la respuesta de /api/dev/v1/asientos.
fecha date Fecha del viaje (Formato: YYYY-MM-DD).
canal_venta string Identificador del canal (Ej: "TU CANAL").
observaciones string No Notas adicionales para este asiento.

Ejemplo de Solicitud

{
    "corrida":"2",
    "origen":"5",
    "destino":"3",
    "horario":"3",
    "asiento":"23",
    "tipo_persona":"adulto",
    "ab": 0,
    "fecha": "2025-10-31",
    "canal_venta":"TU CANAL",
    "observaciones": "Texto que saldrá en los reportes y boleto"
}

Respuesta Exitosa (200 OK)

{
    "cliente": "N/A",
    "asiento_id": "23",
    "horario_id": "3",
    "corrida_id": "2",
    "redondo": null,
    "fecha_reservacion": "2025-10-31",
    "fcomp": 0,
    "origen_id": "5",
    "destino_id": "3",
    "estatus": "apartado",
    "tipo_persona": "adulto",
    "clave": "YGBD2D",
    "ab": false,
    "is_plt_reservacion": true,
    "canal_venta": "TU CANAL",
    "updated_at": "2025-10-30T02:29:45.000000Z",
    "created_at": "2025-10-30T02:29:45.000000Z",
    "id": 3,
    "abordajes": [],
    "expiracion": {
        "fecha_expira": "2025-10-29T20:39:45.666850Z",
        "fecha_expira_diff_h": "en 9 minutos",
        "fecha_expira_mins": 9
    },
    "observaciones": "Texto que saldrá en los reportes y boleto",
    "precio": 350.00
}

Nota Importante sobre la clave

La clave (ej: "YGBD2D") devuelta por este endpoint es **temporal** y se usa para agrupar el asiento en el siguiente paso (/api/dev/v1/reservar).

Al ser usada en /reservar, la API generará una nueva clave final (con caracteres diferentes). Además, si se trata de un viaje redondo, las claves finales tendrán un sufijo: -1 para el viaje de ida y -2 para el viaje de regreso.

POST /api/dev/v1/deseleccionar-asiento

Libera un asiento que fue apartado (con /seleccionar-asiento) antes de que expire su tiempo. Esto cancela la pre-reserva de ese asiento individual.

Cuerpo de la Solicitud (JSON)

Parámetro Tipo Requerido Descripción
clave string La clave única del asiento que se obtuvo en /seleccionar-asiento.

Ejemplo de Solicitud

{
    "clave": "YGBD2D"
}

Respuesta Exitosa (200 OK)

{
    "message": "Clave cancelada"
}

POST /api/dev/v1/reservar

Asigna los datos de un cliente a uno o más asientos apartados (claves). Esto agrupa todas las selecciones de asientos bajo una clave_global y prepara la orden para el pago.

Cuerpo de la Solicitud (JSON)

Parámetro Tipo Requerido Descripción
lang string No Idioma (ej: "es").
cliente object Objeto que contiene la información del cliente.
cliente.nombre string Nombre completo del cliente.
cliente.celular string No Número de celular del cliente.
cliente.email string No Email del cliente (debe ser un email válido si se envía).
reservaciones object Objeto que contiene los arrays de asientos de ida y (opcionalmente) regreso.
reservaciones.ida array Array de objetos, cada uno con la clave temporal de un asiento (obtenida de /seleccionar-asiento).
...ida[].clave string Clave temporal del asiento.
...ida[].observaciones string No Observaciones específicas para este asiento/pasajero.
reservaciones.regreso array No Array de objetos para el viaje de regreso. Requerido si es viaje redondo.
...regreso[].clave string Clave temporal del asiento de regreso.
...regreso[].observaciones string No Observaciones específicas para este asiento/pasajero.
abordajes object No Objeto con los IDs de los puntos de abordaje (puede ser requerido según la configuración).
abordajes.ida integer No ID del punto de abordaje para el viaje de ida.
abordajes.regreso integer No ID del punto de abordaje para el viaje de regreso.
generar_checkout_url boolean No Si es true, el sistema intentará generar un enlace de pago (Stripe/MercadoPago) y lo devolverá en checkout_url. Por defecto false.

Ejemplo de Solicitud (Viaje Redondo)

{
    "cliente": {
        "nombre": "Fulanito Pérez",
        "celular": "9585846497",
        "email": "alejandro@cbus.com.mx"
    },
    "reservaciones": {
        "ida": [
            {
                "clave": "YGBD2D",
                "observaciones": "Pasajero adulto, ventana"
            }
        ],
        "regreso": [
            {
                "clave": "HJD8K1",
                "observaciones": "Pasajero adulto, pasillo"
            }
        ]
    },
    "abordajes": {
        "ida": 1,
        "regreso": 3
    }
}

Respuesta Exitosa (200 OK)

{
    "clave_global": "YBR7HRGL",
    "divisa": "MXN",
    "cliente": {
        "nombre": "Fulanito Pérez",
        "telefono": "9585846497"
    },
    "primer_viaje": false,
    "reservaciones": {
        "ida": [
            {
                "id": 3,
                "cliente": "Fulanito Pérez",
                "celular": "9585846497",
                "email": "alejandro@cbus.com.mx",
                "redondo": true,
                "clave": "YGBD2D-1",
                "observaciones": "Pasajero adulto, ventana"
            }
        ],
        "regreso": [
            {
                "id": 4,
                "cliente": "Fulanito Pérez",
                "celular": "9585846497",
                "email": "alejandro@cbus.com.mx",
                "redondo": true,
                "clave": "YGBD2D-2",
                "observaciones": "Pasajero adulto, pasillo"
            }
        ]
    },
    "expiracion": {
        "fecha_expira": "2025-10-30T06:31:37.360615Z",
        "fecha_expira_diff_h": "en 3 horas",
        "fecha_expira_mins": 120
    },
    "total": 1400.00,
    "total_original": 1552.00,
    "checkout_url": null,
    "checkout_status": null,
    "checkout_requested": false,
    "checkout_error": null,
    "enviar_link_pago": false
}

Errores Posibles (422)

MensajeCausa
Has excedido el tiempo de reservación o las claves no existen, inicia de nuevo. Las claves ya expiraron o no existen en el sistema.
Existen claves duplicadas entre salida y regreso. Una misma clave de asiento aparece en el array de ida y de regreso.
Tu acceso ha sido restringido... El teléfono o email del cliente está en la lista negra del sistema.

POST /api/dev/v1/confirmar

Confirma la reservación y marca todos los boletos asociados a la clave_global como "pagados". Este es el último paso del flujo. La respuesta incluye detalles de los boletos confirmados, información de las sucursales/terminales relevantes al origen del viaje, y los datos fiscales de la empresa transportista.

Cuerpo de la Solicitud (JSON)

Parámetro Tipo Requerido Descripción
clave_global string La clave obtenida en /reservar.
tipo_pago string efectivo, TD, TC, deposito, transferencia.
total numeric No El monto total de la venta con descuentos de comisión. Si se envía, el servidor lo valida contra su cálculo propio (tolerancia de ±$1). Si difiere, retorna error 422.
total_original numeric No El monto total de la venta sin descuentos de comisión. Si se envía, se valida igual que total.
observaciones string No Observaciones generales de la venta que aplicarán para cada una de las claves. (Se recomienda colocar aquí los folios de transacción o de control interno para aclaraciones). Ten en cuenta que al registrar nuevas observaciones, estas sobrescribirán las observaciones previas asignadas desde otros endpoints. Esta información sale en reportes y boletos del cliente.

Ejemplo de Solicitud

{
    "clave_global": "YBR7HRGL",
    "tipo_pago": "TD",
    "total": 700,
    "total_original": 776
    "observaciones": "Folio de transacción MTI1MzU0Ng | Venta relizada por desarrollador independiente"
}

Respuesta Exitosa (200 OK)

La respuesta contiene un array reservaciones con los boletos confirmados, un array sucursales con la información de las terminales asociadas al origen del primer boleto (útil para mostrar dirección, teléfonos, ubicación), y un objeto empresa con los datos fiscales del transportista.

{
    "reservaciones": [
        {
            "id": 3,
            "cliente": "Fulanito Pérez",
            "celular": "9585846497",
            "email": "alejandro@cbus.com.mx",
            "redondo": false,
            "costo_boleto": 700,
            "clave": "YGBD2D", // Clave final del boleto
            "fecha_reservacion": "2025-10-31",
            "fcomp": 0,
            "estatus": "pagado",
            "costo_boleto_original": 776,
            "is_plt_reservacion": 1,
            "tipo_persona": "adulto",
            "corrida_id": 2,
            "asiento_id": 23,
            "horario_id": 3,
            "origen_id": 5,
            "destino_id": 3,
            "ab": 0,
            "created_at": "2025-10-30T02:31:37.000000Z",
            "updated_at": "2025-10-30T02:31:46.000000Z",
            "abordaje_id": null,
            "canal_venta": "TU CANAL",
            "observaciones": "Folio de transacción MTI1MzU0Ng | Venta relizada por desarrollador independiente",
            "origen": {
                "id": 5,
                "nombre": "CDMX"
            },
            "destino": {
                "id": 3,
                "nombre": "OAXACA"
            },
            "abordaje": null,
            "boleto_url": "https://demo.cbus.com.mx/boleto/compartir/YGBD2D"
        }
        // ... más boletos si la clave_global agrupaba varios
    ],
    "sucursales": [ // Información de terminales/sucursales asociadas al origen
         {
            "nombre": "Terminal Central CDMX",
            "direccion": "Av. Principal 123",
            "estado": "Ciudad de México",
            "comision": null, // Campo interno, puede ignorarse
            "codigo_postal": "01234",
            "telefono_1": "5512345678",
            "telefono_2": null,
            "telefono_3": null,
            "descripcion": "Terminal principal en CDMX",
            "pais": "México",
            "colonia": "Centro",
            "localidad": null,
            "municipio": "Cuauhtémoc",
            "lat": "19.4326",
            "lng": "-99.1332"
        }
        // ... puede haber más sucursales relacionadas al origen
    ],
    "empresa": { // Datos fiscales de la empresa transportista
        "nombre": "CBus DEMO",
        "rfc": "ALEJANDRO TONATIUH BERNAL ZAMORANO",
        "razon_social": null,
        "calle": "Jilguero 57",
        "tel_1": "",
        "tel_2": "",
        "tel_3": "",
        "cp": "63170"
    },
    "clave_global": "YBR7HRGL"
}

Errores Posibles

CódigoMensajeCausa
401 Acceso denegado. API key no válida. La API Key no se encontró en el sistema.
403 Acceso denegado. Tu API Key está en MODO PRODUCCIÓN y requiere que configures restricciones... La key está en modo producción pero no tiene restricciones de IP/dominio configuradas en el panel.
422 Tus boletos han sido cancelados, ya no es posible continuar. Los boletos de la clave_global ya no están en estatus apartado (expiraron o fueron cancelados).
422 El total enviado no coincide con el monto calculado en el servidor. El campo total enviado difiere en más de $1 del total calculado por el servidor.
422 El total original enviado no coincide con el monto calculado en el servidor. El campo total_original enviado difiere en más de $1 del calculado por el servidor.

POST /api/dev/v1/boletos/actualizar-nombre

Permite asignar el nombre del pasajero a un boleto que ya fue pagado. Este endpoint está diseñado para flujos donde el pago se confirma antes de que el nombre del pasajero esté disponible. Solo puede ejecutarse una sola vez por boleto y únicamente si la fecha de viaje aún no ha pasado.

Restricciones:
  • El boleto debe tener estatus pagado.
  • La fecha de viaje no debe haber pasado.
  • El nombre solo puede asignarse una vez. Intentos posteriores serán rechazados.

Cuerpo de la Solicitud (JSON)

Parámetro Tipo Requerido Descripción
clave string La clave única del boleto individual (obtenida en /confirmar dentro del array reservaciones).
nombre string Nombre completo del pasajero. Máximo 100 caracteres.

Ejemplo de Solicitud

{
    "clave": "YGBD2D",
    "nombre": "Juan Pérez García"
}

Respuesta Exitosa (200 OK)

{
    "message": "Nombre actualizado correctamente.",
    "clave": "YGBD2D",
    "nombre": "Juan Pérez García"
}

Errores Posibles (422)

Mensaje Causa
El boleto no está pagado. El estatus del boleto no es pagado.
La fecha de viaje ya pasó, no es posible actualizar el nombre. La fecha de viaje registrada en el boleto es anterior a hoy.
El nombre del pasajero ya fue asignado y no puede modificarse nuevamente. El cambio de nombre ya fue realizado previamente en este boleto.

POST /api/dev/v1/boletos/cancelar

Permite cancelar un boleto que ya fue pagado o confirmado. Al ejecutar este endpoint, se cambiará el estatus del boleto a cancelado, y si el pago fue realizado por tarjeta (Stripe), se procesará la cancelación en la pasarela. Además se hará la devolución pertinente de puntos premia si el pasajero contaba con ellos.

Restricciones de tiempo:
  • El sistema validará que la hora de salida de la corrida no haya expirado para poder cancelar.
  • Solo boletos con estatus pagado pueden ser cancelados con este endpoint. (Para boletos en estatus apartado usar /deseleccionar-asiento).

Cuerpo de la Solicitud (JSON)

Parámetro Tipo Requerido Descripción
clave string La clave única del boleto individual.
motivo string Motivo por el cual se cancela el boleto. Máximo 255 caracteres.

Ejemplo de Solicitud

{
    "clave": "YGBD2D",
    "motivo": "Cancelación solicitada por el cliente."
}

Respuesta Exitosa (200 OK)

{
    "message": "Boleto cancelado correctamente.",
    "clave": "YGBD2D"
}

Errores Posibles (422)

Mensaje Causa
El boleto ya se encuentra cancelado. El boleto tiene estatus cancelado previamente.
Solo se pueden cancelar boletos con estatus pagado. El boleto no está pagado.
Ya no puedes cancelar esta reservación (El tiempo de la corrida ha expirado). El tiempo límite para cancelar este boleto ha expirado debido a la hora de salida.

POST /api/dev/v1/envios/preregistrar

Permite a socios comerciales dar de alta un envío en estado de "Pre-registro". Esta modalidad no requiere conocer escalas ni costos, ya que el sistema administrativo lo completará cuando el paquete sea entregado físicamente en una sucursal.

Nota: Este endpoint no genera ticket digital ni PDF.

Cuerpo de la Solicitud (JSON)

Parámetro Tipo Requerido Descripción
remitente string Nombre completo de quien envía.
telefono_remitente string Teléfono de contacto del remitente.
destinatario string Nombre completo de quien recibe.
telefono_destinatario string Teléfono de contacto del destinatario.
descripcion string Contenido del paquete.
modalidad_pago string por_pagar (el cliente paga al dejar) o por_cobrar (paga al recibir).

Ejemplo de Solicitud

{
    "remitente": "Juan Pérez",
    "telefono_remitente": "9511234567",
    "destinatario": "María López",
    "telefono_destinatario": "9519876543",
    "descripcion": "Caja con 5kg de café",
    "modalidad_pago": "por_pagar"
}

Respuesta Exitosa (201 Created)

{
    "success": true,
    "clave": "ENV-784521",
    "mensaje": "Pre-registro guardado. Use esta clave al entregar el paquete en sucursal."
}

GET /api/dev/v1/envios/rastreo

Consulta el estado actual y el historial de seguimiento de un envío (paquetería) a partir de su clave única. Devuelve los datos del envío, las sucursales de origen y destino, y todos los eventos de rastreo registrados en orden cronológico.

Parámetros de Query

Parámetro Tipo Requerido Descripción
clave string Clave única del envío (formato PAQ-XXXXXX), obtenida al crear el preregistro o al registrar el paquete en sucursal.

Ejemplo de Solicitud

GET /api/dev/v1/envios/rastreo?clave=PAQ-784521
Authorization: X-API-KEY tu_api_key

Respuesta Exitosa (200 OK)

{
    "clave": "PAQ-784521",
    "estatus": "en_transito",
    "remitente": "Juan Pérez",
    "destinatario": "María López",
    "sucursal_origen": "Terminal Oaxaca Centro",
    "sucursal_destino": "Terminal Ciudad de México",
    "fecha_envio": "2026-06-01",
    "fecha_entrega": null,
    "seguimiento": [
        {
            "descripcion": "Paquete registrado en sucursal origen",
            "fecha": "2026-06-01 09:15:00"
        },
        {
            "descripcion": "En viaje a sucursal destino PAQ-784521",
            "fecha": "2026-06-01 14:30:00"
        },
        {
            "descripcion": "Recepción en sucursal destino PAQ-784521",
            "fecha": "2026-06-02 08:45:00"
        }
    ]
}

Valores posibles de estatus

Valor Significado
preregistrado Registrado en el sistema, aún no entregado en sucursal.
en_transito El paquete está en camino a la sucursal destino.
en_sucursal_destino El paquete llegó a la sucursal destino y está listo para recoger.
en_intermedio El paquete está en una sucursal intermedia.
entregado El paquete fue entregado al destinatario.

Errores Posibles

Código Mensaje Causa
422 The clave field is required. No se envió el parámetro clave.
404 No se encontró ningún envío con esa clave. La clave no corresponde a ningún envío en el sistema.

POST /api/dev/v1/facturacion/emitir

Genera y timbra una factura CFDI 4.0 para un grupo de boletos ya pagados, identificados por su clave_global. El CFDI se emite ante el SAT en tiempo real a través de Soicont y devuelve las URLs del PDF y XML firmados.

Requisitos previos:
  • Las reservaciones deben tener estatus pagado.
  • No debe existir ya una factura emitida para la clave_global.
  • La clave_global no debe estar incluida en una factura global mensual.
  • El RFC del receptor debe ser válido según el SAT (formato correcto para persona física o moral).

Cuerpo de la Solicitud (JSON)

Parámetro Tipo Requerido Descripción
clave_global string Clave del grupo de boletos (obtenida en /confirmar).
rfc string RFC del receptor. Persona física: 13 caracteres. Moral: 12 caracteres.
nombre string Razón social o nombre completo del receptor tal como está registrado en el SAT.
email string Correo electrónico donde se enviará la notificación de la factura.
uso_cfdi string Clave de uso del CFDI según catálogo SAT. Ej: G03 (Gastos en general), D01 (Honorarios médicos), S01 (Sin efectos fiscales).
forma_pago string Clave de forma de pago SAT. Ej: 01 (Efectivo), 03 (Transferencia), 04 (Tarjeta de crédito), 28 (Tarjeta de débito), 99 (Por definir).
regimen_fiscal string Régimen fiscal del receptor según catálogo SAT. Ej: 626 (Simplificado de Confianza), 612 (Personas Físicas con Act. Empresariales), 601 (General de Ley Personas Morales).
domicilio_fiscal string Código postal del domicilio fiscal del receptor (5 dígitos), tal como aparece en la Constancia de Situación Fiscal del SAT.

Ejemplo de Solicitud

{
    "clave_global": "YBR7HRGL",
    "rfc": "BAZO900101ABC",
    "nombre": "Juan Pérez García",
    "email": "juan.perez@ejemplo.com",
    "uso_cfdi": "G03",
    "forma_pago": "03",
    "regimen_fiscal": "626",
    "domicilio_fiscal": "63170"
}

Respuesta Exitosa (200 OK)

{
    "uuid": "6128a3f1-4e2c-4d1a-9b3e-7c8d2f1e0a45",
    "folio": "1042",
    "pdf": "https://storage.cbus.com.mx/reservaciones/YBR7HRGL/factura.pdf",
    "xml": "https://storage.cbus.com.mx/reservaciones/YBR7HRGL/factura.xml"
}

Errores Posibles (422)

Mensaje Causa
No se encontraron reservaciones pagadas para esta clave_global. La clave_global no existe o los boletos no están en estatus pagado.
Ya existe una factura emitida para esta clave_global. Ya se timbró un CFDI para este grupo de boletos.
Ya no puedes generar factura; estas reservaciones ya están incluidas en una factura global. Las reservaciones ya fueron facturadas de forma global al público en general.
Error al timbrar CFDI: ... El PAC (Soicont) rechazó el comprobante. El mensaje incluye el detalle del error del SAT.

POST /api/dev/v1/facturacion/cancelar

Marca una factura como cancelada en el sistema y libera los boletos asociados para que puedan refacturarse. La cancelación fiscal ante el SAT debe completarse manualmente desde el panel de Soicont (soicont.com.mx), ya que el proveedor no expone un endpoint de cancelación por API.

Importante — dos pasos para cancelar:
  1. Llama a este endpoint para liberar los boletos en el sistema CBus.
  2. Ingresa al panel de Soicont y cancela el CFDI usando el UUID proporcionado para que la cancelación tenga efecto fiscal ante el SAT.

Cuerpo de la Solicitud (JSON)

Parámetro Tipo Requerido Descripción
uuid string UUID del CFDI a cancelar (obtenido en la respuesta de /facturacion/emitir).

Ejemplo de Solicitud

{
    "uuid": "6128a3f1-4e2c-4d1a-9b3e-7c8d2f1e0a45"
}

Respuesta Exitosa (200 OK)

{
    "message": "Factura marcada como cancelada localmente. Completa la cancelación fiscal desde el panel de Soicont (soicont.com.mx) usando el UUID proporcionado.",
    "uuid": "6128a3f1-4e2c-4d1a-9b3e-7c8d2f1e0a45"
}

Errores Posibles

Código Mensaje Causa
404 No se encontró ninguna factura con ese UUID. El UUID no coincide con ninguna factura en el sistema.
422 La factura ya está marcada como cancelada. La cancelación local ya fue aplicada previamente.
422 No puedes cancelar esta factura; forma parte de una factura global. El CFDI fue incluido en una factura global y no puede cancelarse de forma individual.

Manejo de Errores

La API utiliza códigos de estado HTTP estándar para indicar el éxito o fracaso de una solicitud.

422 Unprocessable Entity (Entidad no procesable)

Este es el error más común. Se devuelve cuando la solicitud es sintácticamente correcta, pero no se puede procesar debido a errores de validación o de lógica de negocio.

A) Error de Validación de Campos (Formato Laravel)

Ocurre cuando faltan campos requeridos o los datos no tienen el formato correcto (ej. un email inválido, una fecha mal formada).

{
    "message": "The given data was invalid.",
    "errors": {
        "corrida": [
            "El campo corrida es obligatorio."
        ],
        "horario": [
            "El campo horario es obligatorio."
        ],
        "fecha": [
            "El campo fecha debe ser una fecha posterior o igual a hoy."
        ]
    }
}

B) Error de Lógica de Negocio (Formato Personalizado)

Ocurre cuando los datos son válidos, pero rompen una regla de negocio (ej. el asiento ya está ocupado, el origen y destino son el mismo, la clave de reservación expiró).

{
    "message": "El asiento ya no está disponible"
}
{
    "errors": {
        "message": "Has execedido el tiempo de reservación o las claves no existen, inicia de nuevo."
    }
}

404 Not Found (No Encontrado)

Este error ocurre cuando se intenta acceder a un recurso que no existe, como una URL de endpoint incorrecta.

{
    "message": "Not Found"
}

500 Internal Server Error (Error Interno del Servidor)

Este error indica que algo salió mal en el servidor de forma inesperada (un error en el código, fallo de la base de datos). Este error debe ser reportado al equipo de desarrollo.

{
    "message": "Server Error"
}