Skip to main content

API Reference — Subscriptions & Licenses

Esta sección documenta los endpoints disponibles para la gestión de providers hijos, credenciales, y consulta de licencias y suscripciones.

🔒 Todos los endpoints requieren autenticación. Consulta la sección de Autenticación para obtener tu token y usarlo en la cabecera Authorization: Bearer <token>.


Crear provider hijo​

Permite al provider padre registrar un nuevo provider hijo en la plataforma.

👨🏻‍💻 QA:

POST https://api-qa.speaknosis.com/iam/v1/register/childProvider

🏥 Producción:

POST https://api-prod.speaknosis.com/iam/v1/register/childProvider

Body (application/json):

{
"name": "Clínica Hija",
"country": "Chile",
"city": "Santiago"
}
CampoTipoObligatorioDescripción
namestringSíNombre del establecimiento.
countrystringSíPaís de ubicación.
citystringSíCiudad de ubicación.

Respuesta exitosa (200 OK):

{
"success": true,
"code": 200,
"response": {
"providerId": 1042,
"clientId": "speaknosis-client-xxxxx",
"secret": "xxxxxxxxxxxxxxxxxxxxxxxx"
}
}

⚠️ Guarda el providerId, clientId y secret. Los necesitarás para todos los pasos siguientes.


Obtener credenciales del provider hijo​

Recupera las credenciales de autenticación de un provider hijo existente.

👨🏻‍💻 QA:

GET https://api-qa.speaknosis.com/iam/v1/provider/{childProviderId}/credentials

🏥 Producción:

GET https://api-prod.speaknosis.com/iam/v1/provider/{childProviderId}/credentials
ParámetroUbicaciónTipoObligatorioDescripción
childProviderIdpathstringSíID del provider hijo.

Respuesta exitosa (200 OK):

{
"success": true,
"code": 200,
"response": {
"clientId": "speaknosis-client-xxxxx",
"secret": "xxxxxxxxxxxxxxxxxxxxxxxx"
}
}

Listar providers hijos​

Retorna todos los providers hijos vinculados al provider padre autenticado.

👨🏻‍💻 QA:

GET https://api-qa.speaknosis.com/core/v1/provider/{providerId}/child

🏥 Producción:

GET https://api-prod.speaknosis.com/core/v1/provider/{providerId}/child
ParámetroUbicaciónTipoObligatorioDescripción
providerIdpathstringSíID del provider padre.
limitquerynumberNoCantidad de resultados por página. Si no se envía, retorna todos los hijos.
offsetquerynumberNoCantidad de resultados a saltar. Por defecto: 0.

Ejemplo con paginación:

GET .../child?limit=10&offset=20

Respuesta exitosa (200 OK):

{
"success": true,
"code": 200,
"response": {
"data": [
{
"id": 1042,
"name": "Clínica Hija",
"country": "Chile"
}
],
"meta": {
"total": 45,
"offset": 0,
"limit": 10
}
}
}
CampoTipoDescripción
dataarrayListado de providers hijos.
meta.totalnumberTotal de providers hijos del padre.
meta.offsetnumberOffset aplicado en la consulta.
meta.limitnumberLímite aplicado. Si no se envió limit, coincide con total (se trajo todo).

Suscripciones y Licencias​

Consultar usuarios con sus licencias​

Retorna el listado de doctores registrados en el provider junto con el estado de su licencia asignada.

👨🏻‍💻 QA:

GET https://api-qa.speaknosis.com/payments/v1/subscription/provider/{providerId}/user

🏥 Producción:

GET https://api-prod.speaknosis.com/payments/v1/subscription/provider/{providerId}/user
ParámetroUbicaciónTipoObligatorioDescripción
providerIdpathstringSíID interno del provider.
limitquerynumberNoCantidad de resultados por página. Por defecto: 10.
offsetquerynumberNoCantidad de resultados a saltar. Por defecto: 0.
searchquerystringNoFiltra por nombre o apellido del doctor (coincidencia parcial, sin distinguir mayúsculas).

Ejemplo con paginación:

GET .../user?limit=20&offset=40

Respuesta exitosa (200 OK):

{
"success": true,
"code": 200,
"response": {
"doctors": [
{
"id": "tu-id-externo-1",
"name": "Juan",
"lastname": "Pérez",
"license": {
"type": "BASIC",
"isEnabled": true,
"currentUsage": 12,
"limit": 100,
"expiresAt": "2026-06-01T00:00:00.000Z"
}
},
{
"id": "tu-id-externo-2",
"name": "María",
"lastname": "González",
"license": null
}
],
"suspension": {
"suspended": false
},
"billing": {
"mode": "STRIPE",
"managedBy": null
},
"meta": {
"total": 152,
"offset": 0,
"limit": 20
}
}
}
CampoTipoDescripción
doctors[].idstringID externo del doctor en tu sistema.
doctors[].licenseobjectLicencia asignada. null si el doctor no tiene licencia.
license.typestringTipo de licencia: BASIC, ELITE, TRIAL o USAGE (modalidad por consumo).
license.isEnabledbooleanSi la licencia está activa. Es false cuando el usuario llegó al límite de informes de un plan Basic, cuando la suscripción del provider está suspendida por falta de pago, o cuando el provider padre dio de baja el centro en self-billing (en estos dos últimos casos quedan en false todas las licencias del provider).
license.currentUsagenumberInformes generados en el período actual, incluidas las regeneraciones.
license.limitnumberLímite mensual de informes (100 en Basic). null para los planes ELITE, TRIAL y USAGE (sin límite).
license.expiresAtstringFecha de vencimiento de la licencia.
suspension.suspendedbooleanSi el acceso del provider está cortado por falta de pago. Solo se devuelve para providers que facturan por Stripe; ver la nota de abajo.
suspension.reasonstringMotivo del corte. Presente solo cuando suspended es true; hoy el único valor es payment_failed.
billing.modestringRégimen de facturación del provider: STRIPE si contrata y paga desde el portal de Speaknosis, PARTNER_SELF si le factura su provider padre.
billing.managedBystringNombre del provider que gestiona el cobro cuando mode es PARTNER_SELF. null en el flujo Stripe.
meta.totalnumberTotal de doctores del provider, útil para calcular páginas.
meta.offsetnumberOffset aplicado en la consulta.
meta.limitnumberLímite aplicado. Si no se envió limit, coincide con total (se trajo todo).

ℹ️ suspension según el régimen de facturación. El corte por falta de pago solo existe en el flujo Stripe, así que para un provider con billing.mode = "PARTNER_SELF" el campo suspension no viene. Si tu integración lo lee, trata su ausencia como "sin corte".

Ejemplo de la respuesta para un provider con facturación del padre (solo la parte que cambia):

{
"response": {
"doctors": [],
"billing": {
"mode": "PARTNER_SELF",
"managedBy": "PartnerX"
},
"meta": { "total": 0, "offset": 0, "limit": 20 }
}
}

Consultar licencias del provider​

Permite consultar el resumen de licencias del provider, o filtrar por usuario específico.

👨🏻‍💻 QA:

GET https://api-qa.speaknosis.com/payments/v1/subscription/provider/{providerId}/license

🏥 Producción:

GET https://api-prod.speaknosis.com/payments/v1/subscription/provider/{providerId}/license
ParámetroUbicaciónTipoObligatorioDescripción
providerIdpathstringSíID interno del provider.
userIdquerystringNoID externo del usuario para filtrar sus licencias específicas.
typequerystringNoFiltra por plan: BASIC, ELITE, TRIAL o USAGE. Devuelve el detalle de licencias, no el resumen.

Sin userId — Resumen general​

Retorna el total de licencias contratadas, en uso y disponibles por plan, más información de downgrade programado si existe.

Respuesta exitosa (200 OK):

{
"success": true,
"code": 200,
"response": {
"summary": [
{
"type": "BASIC",
"total": 10,
"used": 7,
"available": 3
},
{
"type": "ELITE",
"total": 5,
"used": 5,
"available": 0
}
],
"scheduledDowngrade": {
"quantity": {
"8": 6,
"9": 3
},
"effectiveDate": "2026-07-01T00:00:00.000Z"
},
"suspension": {
"suspended": false
}
}
}

ℹ️ scheduledDowngrade es null si no hay downgrade programado. suspension.suspended indica si el acceso del provider está cortado por falta de pago, y lleva un reason (hoy solo payment_failed) cuando es true.

Providers con facturación del padre. Cuando el provider factura a través de su padre (billing.mode = "PARTNER_SELF", ver Consultar usuarios con sus licencias), el downgrade programado y el corte por falta de pago no aplican: en su lugar la respuesta trae el estado del contrato.

{
"success": true,
"code": 200,
"response": {
"summary": [
{
"type": "BASIC",
"total": 1,
"used": 0,
"available": 1
}
],
"status": "ACTIVE"
}
}
CampoTipoDescripción
statusstringEstado del contrato: ACTIVE si está vigente (incluido el período de prueba), INACTIVE si el padre lo dio de baja. Solo se devuelve en el flujo PARTNER_SELF.

En ese flujo no vienen scheduledDowngrade ni suspension. Trata la ausencia de cada uno como "no hay downgrade programado" y "sin corte", respectivamente.

summary lista los planes que tienen licencias: un plan dado de baja con quantity: 0 deja de aparecer, y mientras el centro tenga licencias de prueba aparece también una fila TRIAL.

Centros en modalidad por consumo. Si el centro contrató por consumo (👉 ver Self-billing), no tiene cupos: el resumen trae un único plan USAGE, sin total ni disponibles.

{
"success": true,
"code": 200,
"response": {
"contractModel": "USAGE",
"summary": [
{
"type": "USAGE",
"total": null,
"used": 15,
"available": null
}
],
"scheduledSwitch": {
"quantity": { "9": 10 },
"effectiveDate": "2026-12-01 00:00:00"
},
"status": "ACTIVE"
}
}
CampoTipoDescripción
contractModelstringUSAGE para un centro por consumo. Solo se devuelve en esa modalidad.
summary[].usednumberMédicos que ya tienen su licencia de consumo. Cada médico la recibe al abrir la herramienta de grabación por primera vez.
summary[].total / summary[].availablenullEn consumo no hay cupos.
scheduledSwitchobject | nullPaso a cupos pendiente: cantidades por plan (8 = BASIC, 9 = ELITE) y fecha en que se aplica, que es la próxima renovación (UTC, formato AAAA-MM-DD HH:MM:SS). null si no hay ninguno.
statusstringACTIVE o INACTIVE, igual que en el resto del flujo PARTNER_SELF.

Con userId — Licencia de un usuario específico​

Ejemplo:

GET .../license?userId=tu-id-externo-1

Respuesta exitosa (200 OK):

{
"success": true,
"code": 200,
"response": {
"licenses": [
{
"id": 301,
"type": "BASIC",
"limit": 100,
"currentUsage": 12,
"isEnabled": true,
"createdAt": "2026-01-15 10:00:00",
"updatedAt": "2026-05-20 08:30:00",
"lastUsedAt": "2026-05-20 08:30:00",
"expiresAt": "2026-06-01T00:00:00.000Z",
"userId": "tu-id-externo-1",
"name": "Juan",
"lastname": "Pérez"
}
],
"suspension": {
"suspended": false
}
}
}

ℹ️ Igual que en el resumen general, suspension no viene para providers con billing.mode = "PARTNER_SELF".


Webhook del partner (self-billing)​

Solo para providers padre habilitados en régimen self-billing. Permite notificar a Speaknosis las altas, los cambios de cantidad y las bajas de sus centros.

👨🏻‍💻 QA:

POST https://api-qa.speaknosis.com/payments/v1/partner/webhook

🏥 Producción:

POST https://api-prod.speaknosis.com/payments/v1/partner/webhook
Evento (type)Para qué
checkout.session.completedAlta del contrato del centro, por cupos o por consumo
customer.subscription.updatedCambio de cantidad, cambio de modalidad (cupos ↔ consumo), cambio de consultas incluidas, actualización de datos del contrato o reactivación
customer.subscription.deletedBaja: corta el acceso sin borrar datos

Para la modalidad por consumo, data.items lleva un único ítem { "planType": "USAGE", "includedConsultations": 300 }.

El contrato completo —envelope, campos, idempotencia, orden de los eventos, códigos de respuesta, formato de error y política de reintentos— está en 👉 Self-billing.

Un centro que no está configurado para self-billing responde 409.


Códigos de error comunes​

CódigoDescripción
400Cuerpo o parámetros inválidos.
401Token de autenticación faltante o inválido.
403Sin permisos para acceder a este recurso.
404Recurso no encontrado (provider, usuario o licencia inexistente).
409La operación no aplica al régimen de facturación del centro (por ejemplo, contratar por Stripe un centro en self-billing, o enviar al webhook del partner un centro que no lo está). También la devuelve la desasociación de licencias sobre un centro por consumo: en esa modalidad el acceso de un médico se corta dando de baja a su usuario.
422Cuerpo válido pero inaplicable al estado actual del centro. No siempre se resuelve reintentando: en el webhook del partner, la mayoría de los casos requiere una acción (ver Self-billing — Respuestas y errores). Incluye pedir una licencia USAGE para un centro que no está por consumo o que está dado de baja.