GR One - API de Tickets (1.0.0)

Download OpenAPI specification:

API pública del módulo de tickets.

Un ticket representa una solicitud de servicio/asistencia. Los campos adicionales que lleva (form_data) los define un formulario, que se resuelve a partir del alcance (tenant, insurer, assistance_type).

Los nombres de los campos están en inglés; los textos, en español.

Inicio rápido

Dos llamadas bastan para crear un ticket.

1. Verifica la credencial y descubre tu alcance. GET /api/v1/me devuelve, además de tu identidad, el árbol completo de tenants, aseguradoras y tipos de servicio a los que puedes acceder:

curl https://alfa.sistemagrmx.com/api/v1/me \
  -H "Authorization: Bearer $GR_API_KEY"
{
  "id": "018f1a2b-3c4d-7e5f-8a9b-0c1d2e3f4a5b",
  "name": "integracion-demo",
  "permissions": ["tickets.add_ticket", "tickets.view_ticket"],
  "tenants": [
    { "code": "grupo-gr", "title": "Grupo GR",
      "insurers": [
        { "code": "seguros-atlas", "title": "Seguros Atlas",
          "assistance_types": [
            { "code": "vial", "title": "Asistencia vial", "schema_version": 3 }
          ] } ] } ]
}

2. Crea el ticket. Usa cualquier combinación del árbol anterior:

curl -X POST https://alfa.sistemagrmx.com/api/v1/tickets \
  -H "Authorization: Bearer $GR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-4471" \
  -d '{
    "scope":    { "tenant": "grupo-gr", "insurer": "seguros-atlas",
                  "assistance_type": "vial" },
    "location": { "state": "cdmx", "municipality": "Cuauhtémoc" },
    "request":  { "channel": "phone" },
    "contact":  { "caller_name": "Juan Pérez",
                  "contact_phone": "5512345678" },
    "form_data": { "policy_number": "POL-998877", "incident_date": "2026-07-26",
                   "vehicle_make": "Volkswagen", "has_injuries": "No",
                   "atlas_claim_number": "ATL-100245",
                   "vin": "3VWFE21C04M000001" }
  }'

La respuesta trae el folio: "ticket_number": "GR-2026-000137".

3. Encuéntralo después por folio, teléfono o VIN:

curl "https://alfa.sistemagrmx.com/api/v1/tickets?q=5512345678" \
  -H "Authorization: Bearer $GR_API_KEY"

Para construir un formulario de cara al usuario necesitarás además GET /api/v1/forms/resolve, que devuelve el esquema de form_data.

Contrato congelado

Esta es una API versionada para integraciones externas: una vez publicada, ningún campo cambia de nombre, tipo ni significado. Las incorporaciones son siempre aditivas (campos opcionales nuevos); un cambio incompatible sale como /v2.

Identificadores

Ninguna ruta ni cuerpo usa UUID: los UUID cambian entre entornos. Toda referencia a un catálogo tiene la misma forma { code, title }.

Entidad Forma del code Ejemplo
tenant, insurer, assistance_type legible grupo-gr
Resto de catálogos legible cdmx, phone
Ticket folio GR-2026-000137

Todos los code son cadenas legibles y estables. Léelos siempre del catálogo correspondiente en lugar de suponer un formato: no son numéricos y pueden cambiar entre despliegues. La única excepción es gender, cuyo code es una sola letra (M, F, O).

insurer es un tenant con rol de aseguradora, así que su code sale del mismo espacio de nombres que tenant.

Estructura del ticket

El cuerpo del ticket está agrupado por tema, de modo que se lea solo:

Grupo Contenido
scope tenant, aseguradora y tipo de servicio (obligatorio)
location estado y municipio (obligatorio)
request cómo entró la solicitud; channel es obligatorio
contact quién llama y cómo localizarle
policy póliza, razón social y datos del cliente
handling estatus de asignación y descripción de la asistencia
form_data respuestas del formulario del tipo de servicio

handling es lo que se actualiza con más frecuencia: PATCH {"handling": {"assignment_status": "assigned"}}.

Un tenant no puede asegurar su propia solicitud: scope.tenant y scope.insurer tienen que ser distintos. Enviarlos iguales devuelve 422 invalid_scope.

Formularios versionados

form_data se valida contra un formulario que los administradores pueden editar. El formulario no pertenece al tipo de servicio: pertenece al alcance completo (tenant, insurer, assistance_type) — dos aseguradoras pueden tener formularios distintos para el mismo tipo de servicio.

Por defecto no hay que hacer nada: la API valida contra el formulario vigente. Si te importa detectar que el formulario cambió bajo tus pies, envía la versión que conoces como precondición:

If-Schema-Version: 3

Si esa versión ya no es la vigente, la respuesta es 409 schema_version_rejected con la versión actual en error.details: vuelve a resolver el formulario, reconstruye form_data y reintenta. Toda respuesta de ticket incluye el schema_version con el que se validó.

El VIN (número de serie del vehículo) no es un campo de primer nivel: viaja en form_data bajo la clave vin, que es la que consulta la búsqueda.

Campos genéricos

Los formularios se arman con definiciones de campo reutilizables (key, label, data_type) repartidas en capas: una global, y otras por tipo de servicio, tenant y aseguradora.

GET /api/v1/forms/resolve es el único endpoint de formularios: pliega las capas que impliquen los parámetros que mandes y devuelve items (etiquetas y orden) junto con schema (JSON Schema draft-07, la validación). Sin parámetros obtienes la capa global; con ?assistance_type=vial, global + esa capa; añadiendo tenant y/o insurer se pliegan también las suyas.

Así puedes pintar y validar un formulario antes de conocer el alcance completo. La contrapartida es el versionado: schema_version sólo viene cuando envías los tres ejes, porque sólo entonces el formulario corresponde a un alcance con el que se puede crear un ticket. Cachea contra If-Schema-Version únicamente esas respuestas.

Búsqueda y filtros

GET /api/v1/tickets filtra y busca. Los filtros explícitos (ticket_number, vin, phone, assistance_type, …) se combinan con Y lógico. q es el atajo para una caja de búsqueda única: su formato determina automáticamente contra qué se busca.

Se detecta como Cuando q Campos consultados
vin son 17 caracteres [A-HJ-NPR-Z0-9] (sin I, O, Q) clave vin de form_data
phone deja exactamente 10 dígitos al quitar separadores contact_phone, driver_phone
ticket_number tiene la forma GR-AAAA-NNNNNN ticket_number

Cuando se usa q, la respuesta incluye matched_by. Versiones futuras pueden reconocer formatos adicionales: si eso te afecta, usa los filtros explícitos, que son el camino estable.

Para sincronización incremental usa updated_since: devuelve sólo los tickets modificados después de esa marca de tiempo, en vez de recorrer todo el historial.

Una consulta sin coincidencias no es un error: devuelve 200 con items: [] y total: 0.

Autenticación

Todos los endpoints requieren una API key en el encabezado Authorization, con el esquema Bearer:

Authorization: Bearer grk_xxxxxxxxxxxxxxxxxxxx

Una API key ausente o inválida devuelve 401 unauthorized; una válida sin permisos suficientes devuelve 403 forbidden.

Errores

Todas las respuestas de error comparten el mismo envoltorio Error, con un code estable para decidir por programa, un message legible y el request_id de la petición (cítalo al reportar incidencias).

code HTTP
bad_request 400
unauthorized 401
forbidden 403
not_found 404
combination_unavailable 409
schema_version_rejected 409
unsupported_media_type 415
invalid_scope 422
validation_failed 422
form_validation_failed 422
search_term_unrecognized 422
rate_limited 429

Cuando el fallo es campo por campo, error.details[] lo desglosa con su propio code estable: required, max_length, min_length, pattern, invalid_option, unknown_field, outdated.

Límites de uso

Las peticiones se limitan por integración, no por dirección IP: todas las API keys de una misma integración comparten el mismo límite, de modo que emitir claves nuevas no amplía la cuota. Cada respuesta incluye X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset; al superar el límite se devuelve 429 rate_limited con Retry-After, los segundos que hay que esperar antes de reintentar.

Reintentos e idempotencia

POST /api/v1/tickets acepta el encabezado opcional Idempotency-Key (cualquier cadena tuya: número de pedido, UUID, lo que uses). Repetir la petición con la misma clave dentro de 24 h devuelve la respuesta original en lugar de crear un ticket duplicado. Sin ese encabezado, un reintento tras un timeout puede duplicar el ticket.

Identidad

Verificación de la API key, sus permisos y su alcance.

Consultar identidad y alcance

Devuelve la identidad de la API key, sus permisos efectivos y el árbol completo de alcance: los tenants a los que puede acceder, sus aseguradoras y los tipos de servicio de cada par, con la versión vigente del formulario de cada uno.

Es la única llamada necesaria antes de crear un ticket: de aquí salen los tres code que van en scope. Cachea el resultado y vuelve a pedirlo cuando un schema_version te resulte obsoleto.

Si una API key llega a tener un alcance muy grande, usa en su lugar los endpoints individuales de /api/v1/catalogs.

Authorizations:
BearerApiKey

Responses

Request samples

curl https://alfa.sistemagrmx.com/api/v1/me \
  -H "Authorization: Bearer $GR_API_KEY"

Response samples

Content type
application/json
{
  • "id": "018f1a2b-3c4d-7e5f-8a9b-0c1d2e3f4a5b",
  • "name": "integracion-demo",
  • "permissions": [
    ],
  • "tenants": [
    ]
}

Tickets

Creación, consulta, filtrado y actualización de tickets.

Listar, filtrar y buscar tickets

Lista paginada de tickets, más reciente primero. Todos los filtros son opcionales y se combinan con Y lógico.

Búsqueda rápida

q es el atajo para una caja de búsqueda única: detecta el formato del término y busca contra el identificador correspondiente.

Se detecta como Cuando q Campos consultados Coincidencia
vin son 17 caracteres ^[A-HJ-NPR-Z0-9]{17}$ (sin I, O, Q) clave vin de form_data exacta, sin distinguir mayúsculas
phone deja exactamente 10 dígitos tras quitar espacios, +, -, ( y ) contact_phone, driver_phone exacta sobre los 10 dígitos normalizados
ticket_number coincide con ^GR-\d{4}-\d{6}$, sin distinguir mayúsculas ticket_number exacta

Cuando se usa q, la respuesta incluye matched_by. Si el formato no corresponde a ninguno, la respuesta es 422 search_term_unrecognized: usa entonces el filtro explícito.

Versiones futuras pueden reconocer formatos adicionales, así que vin, phone y ticket_number son el camino estable.

Sincronización incremental

updated_since devuelve sólo lo modificado después de esa marca de tiempo. Es la forma correcta de mantener una copia al día sin recorrer todo el historial en cada pasada.

Sin resultados

No es un error: 200 con items: [] y total: 0.

Authorizations:
BearerApiKey
query Parameters
q
string [ 3 .. 64 ] characters
Examples:
  • q=5512345678 - Por teléfono
  • q=3VWFE21C04M000001 - Por VIN
  • q=GR-2026-000137 - Por folio

Término único de búsqueda: folio, teléfono o VIN. Su formato determina el campo consultado.

ticket_number
string^GR-\d{4}-\d{6}$
Example: ticket_number=GR-2026-000137

Filtro exacto por folio.

vin
string^[A-HJ-NPR-Z0-9]{17}$
Example: vin=3VWFE21C04M000001

Filtro exacto por la clave vin de form_data.

phone
string
Example: phone=5512345678

Filtro por teléfono, contra contact_phone y driver_phone. Se normaliza a los 10 dígitos nacionales.

tenant
string
Example: tenant=grupo-gr

code del tenant.

insurer
string
Example: insurer=seguros-atlas

code de la aseguradora.

assistance_type
string
Example: assistance_type=vial

code del tipo de servicio.

assignment_status
string
Example: assignment_status=assigned

code del estatus de asignación.

created_from
string <date-time>
Example: created_from=2026-07-01T00:00:00Z

Sólo tickets creados en o después de esta fecha/hora.

created_to
string <date-time>
Example: created_to=2026-07-31T23:59:59Z

Sólo tickets creados en o antes de esta fecha/hora.

updated_since
string <date-time>
Example: updated_since=2026-07-25T00:00:00Z

Sólo tickets modificados después de esta fecha/hora. Úsalo para sincronización incremental.

limit
integer [ 1 .. 100 ]
Default: 20

Cantidad máxima de registros por página. Un valor mayor que 100 se recorta a 100; no es un error.

offset
integer >= 0
Default: 0

Cantidad de registros a saltar antes de empezar la página.

Responses

Request samples

# Caja de búsqueda única
curl "https://alfa.sistemagrmx.com/api/v1/tickets?q=5512345678" \
  -H "Authorization: Bearer $GR_API_KEY"

# Sincronización incremental
curl "https://alfa.sistemagrmx.com/api/v1/tickets?updated_since=2026-07-25T00:00:00Z&limit=100" \
  -H "Authorization: Bearer $GR_API_KEY"

Response samples

Content type
application/json
Example
{
  • "matched_by": "phone",
  • "items": [
    ],
  • "limit": 20,
  • "offset": 0,
  • "total": 1
}

Crear un ticket

Crea un ticket de servicio. Los tres code de scope salen de GET /api/v1/me.

form_data lleva las respuestas del formulario del alcance. Por defecto se valida contra el formulario vigente; no hace falta declarar ninguna versión. Si quieres detectar que el formulario cambió, envía If-Schema-Version con la versión que conoces y recibirás 409 schema_version_rejected en lugar de una validación contra un formulario que no esperabas.

Un fallo de form_data devuelve 422 form_validation_failed con el detalle campo por campo en error.details.

Envía Idempotency-Key para que un reintento no duplique el ticket.

Authorizations:
BearerApiKey
header Parameters
Idempotency-Key
string <= 255 characters
Example: pedido-4471

Clave de idempotencia: cualquier cadena tuya (número de pedido, UUID, etc.). Repetir la petición con la misma clave dentro de 24 h devuelve la respuesta original en lugar de crear un ticket duplicado.

If-Schema-Version
integer >= 1
Example: 3

Precondición opcional: la versión del formulario contra la que se construyó form_data. Si ya no es la vigente, la petición falla con 409 schema_version_rejected en lugar de validarse contra un formulario distinto del esperado.

Omitirlo valida contra el formulario vigente, que es el comportamiento por defecto.

Request Body schema: application/json
required
required
object (TicketScopeInput)

Alcance del ticket, por code.

required
object (TicketLocationInput)

Ubicación administrativa del servicio.

required
object (TicketRequestInput)

Cómo entró la solicitud y quién la registró.

object (TicketContactInput)

Quién solicita el servicio y cómo localizarle.

object (TicketPolicy)

Datos de póliza y del cliente.

object (TicketHandlingInput)

Estado de atención del ticket. Es el grupo que más se actualiza.

object or null

Respuestas del formulario del alcance. Las claves son las del esquema que devuelve GET /api/v1/forms/resolve. El VIN va en la clave vin.

Responses

Request samples

Content type
application/json
Example
{
  • "scope": {
    },
  • "location": {
    },
  • "request": {
    },
  • "form_data": {
    }
}

Response samples

Content type
application/json
{
  • "ticket_number": "GR-2026-000137",
  • "schema_version": 3,
  • "scope": {
    },
  • "location": {
    },
  • "request": {
    },
  • "contact": {
    },
  • "policy": {
    },
  • "form_data": {
    },
  • "created_at": "2026-07-25T14:32:10Z",
  • "updated_at": "2026-07-25T14:32:10Z"
}

Consultar un ticket

Devuelve un ticket por su folio. Un ticket_number inexistente devuelve 404 not_found.

Authorizations:
BearerApiKey
path Parameters
ticket_number
required
string^GR-\d{4}-\d{6}$
Example: GR-2026-000137

Folio del ticket, p. ej. GR-2026-000137.

Responses

Request samples

curl https://alfa.sistemagrmx.com/api/v1/tickets/GR-2026-000137 \
  -H "Authorization: Bearer $GR_API_KEY"

Response samples

Content type
application/json
{
  • "ticket_number": "GR-2026-000137",
  • "schema_version": 3,
  • "scope": {
    },
  • "location": {
    },
  • "request": {
    },
  • "contact": {
    },
  • "policy": {
    },
  • "handling": {
    },
  • "form_data": {
    },
  • "created_at": "2026-07-25T14:32:10Z",
  • "updated_at": "2026-07-25T15:04:51Z"
}

Actualizar un ticket

Actualización parcial. Lo habitual es mover el estatus:

{ "handling": { "assignment_status": "assigned",
                "assistance": "Grúa en camino, ETA 25 min" } }

Semántica: un grupo ausente deja intactos todos sus campos; una clave ausente dentro de un grupo presente también; una clave con valor null borra el campo (sólo en campos opcionales). Enviar {} no cambia nada.

form_data se reemplaza completo, no se fusiona clave por clave. Igual que al crear, puedes enviar If-Schema-Version para que la petición falle si el formulario cambió.

Authorizations:
BearerApiKey
path Parameters
ticket_number
required
string^GR-\d{4}-\d{6}$
Example: GR-2026-000137

Folio del ticket, p. ej. GR-2026-000137.

header Parameters
If-Schema-Version
integer >= 1
Example: 3

Precondición opcional: la versión del formulario contra la que se construyó form_data. Si ya no es la vigente, la petición falla con 409 schema_version_rejected en lugar de validarse contra un formulario distinto del esperado.

Omitirlo valida contra el formulario vigente, que es el comportamiento por defecto.

Request Body schema: application/json
required
object or null

Alcance del ticket, por code.

object or null

Ubicación administrativa del servicio.

object or null

Cómo entró la solicitud y quién la registró.

object (TicketContactInput)

Quién solicita el servicio y cómo localizarle.

object (TicketPolicy)

Datos de póliza y del cliente.

object (TicketHandlingInput)

Estado de atención del ticket. Es el grupo que más se actualiza.

object or null

Reemplaza el objeto completo; no se fusiona clave por clave.

Responses

Request samples

Content type
application/json
Example
{
  • "handling": {
    }
}

Response samples

Content type
application/json
{
  • "ticket_number": "GR-2026-000137",
  • "schema_version": 3,
  • "scope": {
    },
  • "location": {
    },
  • "request": {
    },
  • "contact": {
    },
  • "handling": {
    },
  • "form_data": {
    },
  • "created_at": "2026-07-25T14:32:10Z",
  • "updated_at": "2026-07-25T15:12:03Z"
}

Catálogos

Entidades del alcance (tenants, aseguradoras, tipos de servicio) y catálogos simples de valores.

Listar los catálogos disponibles

Índice de catálogos. Cada entrada indica qué parámetros de alcance hay que enviar para consultarlo, de modo que una sola llamada baste para saber cómo recorrer el resto.

Para el flujo normal de integración no hace falta: GET /api/v1/me ya devuelve el alcance completo.

Authorizations:
BearerApiKey

Responses

Request samples

curl https://alfa.sistemagrmx.com/api/v1/catalogs \
  -H "Authorization: Bearer $GR_API_KEY"

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "total": 9
}

Listar los tenants accesibles

Tenants sobre los que puede actuar la API key.

GET /api/v1/me ya los devuelve junto con sus aseguradoras y tipos de servicio; este endpoint existe para keys con un alcance demasiado grande para traerlo entero.

Authorizations:
BearerApiKey

Responses

Request samples

curl https://alfa.sistemagrmx.com/api/v1/catalogs/tenants \
  -H "Authorization: Bearer $GR_API_KEY"

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "total": 2
}

Listar las aseguradoras de un tenant

Aseguradoras relacionadas con el tenant indicado. Un tenant al que la API key no tiene acceso devuelve 403 forbidden.

Authorizations:
BearerApiKey
query Parameters
tenant
required
string
Example: tenant=grupo-gr

code del tenant (cliente) del alcance.

Responses

Request samples

curl "https://alfa.sistemagrmx.com/api/v1/catalogs/insurers?tenant=grupo-gr" \
  -H "Authorization: Bearer $GR_API_KEY"

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "total": 2
}

Listar los tipos de servicio de un tenant y una aseguradora

Tipos de servicio disponibles para el par (tenant, insurer), cada uno con la versión vigente de su formulario.

Comparar ese schema_version con el que tienes en caché te dice si necesitas volver a llamar a GET /api/v1/forms/resolve.

Authorizations:
BearerApiKey
query Parameters
tenant
required
string
Example: tenant=grupo-gr

code del tenant (cliente) del alcance.

insurer
required
string
Example: insurer=seguros-atlas

code de la aseguradora del alcance.

Responses

Request samples

curl "https://alfa.sistemagrmx.com/api/v1/catalogs/assistance-types?tenant=grupo-gr&insurer=seguros-atlas" \
  -H "Authorization: Bearer $GR_API_KEY"

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "total": 2
}

Obtener los valores de un catálogo simple

Opciones de un catálogo simple, cada una con la forma { code, title }. El code es el valor que va en el campo correspondiente del ticket (p. ej. el code de state se envía en location.state).

Los catálogos con alcance (tenants, insurers, assistance-types) tienen sus propias rutas. Los campos genéricos no son un catálogo: viven en GET /api/v1/forms/resolve.

Authorizations:
BearerApiKey
path Parameters
catalog
required
string
Enum: "state" "request_channel" "request_type" "gender" "assignment_status" "request_mode"
Example: state

Nombre del catálogo simple a consultar.

Responses

Request samples

curl https://alfa.sistemagrmx.com/api/v1/catalogs/state \
  -H "Authorization: Bearer $GR_API_KEY"

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "total": 32
}

Formularios

Campos y esquema de form_data para un alcance, completo o parcial. La versión (schema_version) sólo acompaña al alcance completo.

Resolver el formulario de un alcance

Devuelve el formulario de form_data vigente para los ejes de alcance que envíes: etiquetas y orden en items, validación en schema (JSON Schema draft-07).

El alcance se pliega por capas, y la global entra siempre:

Parámetros Capas plegadas
ninguno global
?assistance_type=vial global + vial
?tenant=grupo-gr&assistance_type=vial global + vial + grupo-gr + grupo-gr·vial
los tres todas, y la respuesta lleva schema_version

Así puedes pintar y validar un formulario antes de conocer el alcance completo: «¿qué campos lleva un servicio vial?» se responde con ?assistance_type=vial, sin tenant ni aseguradora.

  • items y las propiedades de schema describen siempre el mismo conjunto de campos. items va en orden de presentación; los campos excluidos en el alcance no aparecen en ninguno de los dos.
  • schema_version sólo aparece con los tres ejes. Su presencia significa que ese formulario corresponde a un alcance con el que se puede crear un ticket, y sólo esa respuesta sirve para If-Schema-Version. Un alcance parcial no lleva versión porque puede no corresponder a ningún formulario concreto.
  • Por lo mismo, 409 combination_unavailable sólo puede ocurrir cuando envías los tres: es la comprobación de que la combinación tenant/aseguradora/servicio existe.

Para crear un ticket no hace falta llamar aquí: basta con GET /api/v1/me.

Authorizations:
BearerApiKey
query Parameters
tenant
string
Example: tenant=grupo-gr

code del tenant, para acotar el resultado a su alcance.

insurer
string
Example: insurer=seguros-atlas

code de la aseguradora, para acotar el resultado a su alcance.

assistance_type
string
Example: assistance_type=vial

code del tipo de servicio, para acotar el resultado a su alcance.

Responses

Request samples

# Formulario de un tipo de servicio, sin tenant ni aseguradora
curl "https://alfa.sistemagrmx.com/api/v1/forms/resolve?assistance_type=vial" \
  -H "Authorization: Bearer $GR_API_KEY"

Response samples

Content type
application/json
Example

Sólo assistance_type: se pliegan la capa global y la de vial. Sirve para pintar y validar, pero no lleva schema_version, porque no corresponde a ningún alcance con el que se pueda crear un ticket.

En schema se muestran las restricciones esenciales de cada campo; la respuesta real añade además envoltorios que aceptan o rechazan el valor vacío según required.

{
  • "scope": {
    },
  • "items": [
    ],
  • "schema": {
    }
}