Docs

API: citas y viajes

Consultar la agenda de citas y los viajes con sus estados, filtros por cliente, empleado o conductor, sus webhooks y todos sus campos.

Revisado el 28 de octubre de 2026

La cita

json
{
  "id": "d6e7f8a9-b0c1-4d2e-9f3a-4b5c6d7e8f9a",
  "object": "appointment",
  "status": "confirmed",
  "service": "Corte de cabello",
  "starts_at": "2026-10-02T15:00:00.000Z",
  "duration_minutes": 45,
  "price": "8.00",
  "deposit_amount": "2.00",
  "employee": { "id": "e7f8a9b0-c1d2-4e3f-8a4b-5c6d7e8f9a0b", "name": "Ana" },
  "note": null,
  "customer": {
    "id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f",
    "name": "María Pérez",
    "phone": "584140000000"
  },
  "created_at": "2026-09-29T13:40:10.221Z",
  "updated_at": "2026-09-29T13:52:02.876Z"
}
CampoTipoQué es
iduuidId de la cita.
objecttextoSiempre appointment.
statustextoVer estados.
servicetextoEl servicio, con el nombre que tenía al agendarse.
starts_atfechaCuándo empieza (UTC).
duration_minutesenteroCuánto dura.
priceimporte | nullPrecio del servicio, en la moneda base del negocio.
deposit_amountimporte | nullLa seña (anticipo) que hay que pagar para confirmarla, si el negocio la pide.
employeeobjeto | null{ id, name } de quien atiende, o null si no se asignó.
notetexto | nullNota de la cita.
locationtextolocal (en el negocio) o home (a domicilio).
addresstexto | nullA domicilio: la dirección.
optionslistaLo que eligió del servicio: { group, name, price, minutes? }. Vacía si nada.
travel_feeimporte | nullA domicilio: el traslado cobrado. Ya va dentro de price.
customerobjetoResumen del cliente: { id, name, phone }.
created_atfechaCuándo se agendó.
updated_atfechaÚltimo cambio.

Estados de una cita

statusEn el panelQué significa
requestedSolicitadaPedida por el cliente, todavía sin confirmar (por ejemplo, esperando la seña).
confirmedConfirmadaConfirmada.
completedCompletadaSe atendió.
cancelledCanceladaCancelada.
no_showNo asistióEl cliente no vino.

Una cita con seña se confirma sola cuando los pagos aprobados cubren la seña (puede pagarse en varias partes; ver aprobar un comprobante); si el pago se rechaza, queda en requested.

Listar, ver y agendar citas

GET/appointments

Ordenadas por la hora de inicio, de la más tardía a la más temprana, paginado. Filtros opcionales:

ParámetroQué filtra
statusUno de los estados de arriba. Otro valor: 400 invalid_status.
fromCitas que empiezan en esa fecha o después (ISO 8601). Mal escrita: 400 invalid_date.
toCitas que empiezan en esa fecha o antes.
customer_idLas citas de un cliente. Si no es un uuid: 400 invalid_id.
employee_idLas de quien atiende (el employee.id de la cita). Si no es un uuid: 400 invalid_id.
updated_afterLas que cambiaron en esa fecha o después. Ojo: con este filtro el orden pasa a ser del cambio más viejo al más nuevo, no por hora de inicio (ver sincronización incremental).
curl
# La agenda confirmada del 2 de octubre (hora de Venezuela)
curl "https://api.atiendefacil.com/api/v1/appointments?status=confirmed&from=2026-10-02T00:00:00-04:00&to=2026-10-02T23:59:59-04:00" \
  -H "Authorization: Bearer af_live_TU_CLAVE_AQUI"
GET/appointments/{id}

Una cita. Una cita borrada responde 404.

POST/appointments

Agenda una cita. Hace lo mismo que Nueva cita en el panel y la app: nace confirmed y se le avisa a quien la atiende. Responde 201 con la cita.

CampoObligatorioQué es
customer_id / customer_phoneUno de los dosEl cliente. Con el teléfono, si no existe se crea; customer_name es opcional.
serviceSíEl servicio. Si coincide con uno del catálogo, toma su precio y duración.
starts_atSíISO 8601. Sin zona se lee en la hora del negocio. No puede ser en el pasado.
duration_minutesNoDe 5 a 480. Por defecto, la del servicio o 30.
employee_idNoQuién atiende (su id en GET /team). Tiene que atender al público.
noteNoNota de la cita.
locationNolocal (por defecto) o home. A domicilio hace falta que el negocio lo tenga activado; se suma el traslado.
addressNoA domicilio: la dirección. Sin ella, la que tenga el cliente.
optionsNoOpciones del servicio: [{ group?, name }]. Si el servicio tiene un grupo obligatorio, hay que mandarlo.
curl
curl -X POST https://api.atiendefacil.com/api/v1/appointments \
  -H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_phone": "04140000000",
    "customer_name": "María González",
    "service": "Corte de cabello",
    "starts_at": "2026-10-05T15:00:00-04:00",
    "duration_minutes": 45
  }'

Errores: 400 invalid_body (también si el negocio está cerrado o esa persona no atiende a esa hora), 409 slot_unavailable (ya hay una cita o no queda cupo) y 404 not_found (cliente o empleado).

Horas libres

GET/appointments/availability

Los ratos libres de un rango, dentro del horario de atención: los mismos huecos que enseña la agenda del panel. Úsalo para ofrecer horas antes de agendar. Sin paginar.

ParámetroObligatorioQué es
from, toSíEl rango, en ISO 8601 (sin zona se lee en la hora del negocio). Hasta 31 días.
employee_idNoLos huecos de esa persona, contra su horario. Sin él, los del negocio contando las citas de todos.
min_minutesNoSolo los huecos de al menos esos minutos (0 a 480).
curl
curl "https://api.atiendefacil.com/api/v1/appointments/availability?from=2026-10-12T00:00:00&to=2026-10-13T00:00:00&min_minutes=30" \
  -H "Authorization: Bearer af_live_TU_CLAVE_AQUI"
json
{
  "data": [
    { "object": "slot", "starts_at": "2026-10-12T13:00:00.000Z", "ends_at": "2026-10-12T15:30:00.000Z", "minutes": 150 },
    { "object": "slot", "starts_at": "2026-10-12T16:15:00.000Z", "ends_at": "2026-10-12T22:00:00.000Z", "minutes": 345 }
  ]
}
  • Solo salen huecos futuros y de 15 minutos o más. Un día cerrado no trae ninguno.
  • Cada hueco es un tramo continuo: en uno de 10:00 a 12:00 cabe una cita de 30 minutos a las 10:00, a las 10:30, a las 11:00…
  • Es una guía, no una reserva: quien decide es POST /appointments, que puede responder 409 slot_unavailable si alguien tomó la hora entre medias. Si el negocio atiende varias citas a la vez (cupo mayor que 1) y no mandas employee_id, puede caber una cita en una hora que aquí no sale libre.
  • Rango mal escrito, al revés o de más de 31 días: 400 invalid_date.

Mover, confirmar o cancelar una cita

PATCH/appointments/{id}

Cambia una cita, como al editarla en el panel o la app. Manda solo lo que cambia. Responde la cita.

CampoQué es
statusconfirmed (confirmarla), cancelled (cancelarla), completed (se atendió), no_show (no vino) o requested.
starts_atLa hora nueva. ISO 8601; sin zona, la del negocio. No puede ser en el pasado.
duration_minutesDe 5 a 480.
employee_idQuién atiende, o null para dejarla sin asignar.
serviceEl nombre del servicio. No cambia el precio que ya tenía la cita.
noteLa nota, o null para borrarla.
curl
# Mover la cita al día siguiente a las 10:00 y dejarla confirmada
curl -X PATCH https://api.atiendefacil.com/api/v1/appointments/d6e7f8a9-b0c1-4d2e-8f3a-4b5c6d7e8f9a \
  -H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
  -H "Content-Type: application/json" \
  -d '{ "starts_at": "2026-10-06T10:00:00-04:00", "status": "confirmed" }'
  • Al cambiar la hora, la duración o quién atiende se valida lo mismo que al agendar: 400 invalid_body (pasado, negocio cerrado o esa persona no atiende a esa hora) y 409 slot_unavailable (solape o sin cupo).
  • Mover la hora reinicia los recordatorios: al cliente se le recordará la hora nueva.
  • Este cambio no le escribe al cliente: si mueves o cancelas una cita, avísale tú con POST /messages.
  • Salen appointment.status_changed y/o appointment.rescheduled, según lo que cambie.
  • No hay borrado por la API: una cita que no va se cancela.

Webhooks de citas

EventoCuándo sale
appointment.createdSe agendó una cita.
appointment.status_changedCambió el estado. Trae status y previous_status.
appointment.rescheduledCambió la hora, la duración, el servicio o quién atiende. Trae previous_starts_at. Si en el mismo cambio también cambió el estado (por ejemplo, la cita movida vuelve a requested), salen los dos: appointment.status_changed y appointment.rescheduled.
appointment.deletedSe borró. El objeto solo trae el id.

Ejemplos de cada uno en Webhooks.

El viaje

En modo transporte, cada vehículo pedido es un viaje: si el cliente pide tres motos de una vez, son tres viajes con el mismo group.id, cada uno con su conductor y su tarifa.

json
{
  "id": "f8a9b0c1-d2e3-4f4a-9b5c-6d7e8f9a0b1c",
  "object": "ride",
  "status": "in_progress",
  "vehicle_type": "mototaxi",
  "vehicle_class": "Moto",
  "pickup": { "address": "C.C. Sambil, entrada principal", "lat": 10.4893, "lng": -66.8532 },
  "dropoff": { "address": "Plaza Altamira", "lat": 10.4961, "lng": -66.8487 },
  "distance_km": "4.10",
  "duration_minutes": 12,
  "fare": "3.00",
  "currency": "USD",
  "driver": { "id": "a9b0c1d2-e3f4-4a5b-8c6d-7e8f9a0b1c2d", "name": "Pedro" },
  "group": { "id": null, "index": 1, "size": 1 },
  "payment": { "method": null, "reference": null, "amount": null, "paid_at": null },
  "cancel_reason": null,
  "customer": {
    "id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f",
    "name": "María Pérez",
    "phone": "584140000000"
  },
  "accepted_at": "2026-09-29T19:02:44.310Z",
  "started_at": "2026-09-29T19:09:12.005Z",
  "completed_at": null,
  "cancelled_at": null,
  "created_at": "2026-09-29T19:02:01.771Z",
  "updated_at": "2026-09-29T19:09:12.005Z"
}
CampoTipoQué es
iduuidId del viaje.
objecttextoSiempre ride.
statustextoVer estados.
vehicle_typetextoLa familia de vehículo: mototaxi (moto) o taxi (carro). Es el valor interno; en pantalla el negocio ve el nombre de la clase.
vehicle_classtexto | nullLa clase que pidió el cliente, con el nombre que le puso el negocio («Moto», «Carro cómodo»).
pickupobjeto{ address, lat, lng } del punto de recogida. Cualquiera puede ser null.
dropoffobjetoLo mismo, del destino.
distance_kmimporte | nullDistancia del recorrido, en km.
duration_minutesentero | nullDuración estimada del recorrido.
fareimporteTarifa del viaje.
commissionimporteLo que se queda la agencia de esa tarifa.
driver_earningsimporteLo que recibe el conductor (tarifa menos comisión).
currencytextoMoneda de la tarifa.
driverobjeto | null{ id, name } del conductor, o null mientras nadie lo tomó.
tracking_urltexto | nullEl enlace de seguimiento en vivo que recibe el pasajero. null si el negocio lo tiene apagado o el viaje ya terminó.
groupobjetoid de la solicitud (null si fue un solo vehículo), index (cuál de ellos es) y size (cuántos se pidieron).
paymentobjetoCómo se pagó: method (pago_movil, transferencia, cash o null), reference, amount (importe) y paid_at.
cancel_reasontexto | nullPor qué se canceló.
customerobjetoResumen del cliente: { id, name, phone }.
accepted_atfecha | nullCuándo lo aceptó el conductor. Igual started_at (empezó), completed_at y cancelled_at.
created_atfechaCuándo se pidió.
updated_atfechaÚltimo cambio.

Estados de un viaje

statusEn el panelQué significa
pending_paymentEsperando pagoViaje con cobro por adelantado: existe y tiene tarifa, pero no se le ofrece a ningún conductor hasta que se confirme el pago.
requestedBuscando conductorSe está ofreciendo a los conductores en turno.
acceptedAceptadoUn conductor lo tomó.
driver_arrivingLlegandoEl conductor va hacia el punto de recogida.
in_progressEn viajeEl pasajero (o el envío) va en camino al destino.
completedCompletadoTerminó. Es final.
cancelledCanceladoSe canceló. Es final; el motivo va en cancel_reason.

Listar, ver y crear viajes

GET/rides

Del más nuevo al más viejo, paginado. Filtros opcionales (se combinan):

ParámetroQué filtra
statusUno de los estados de arriba. Otro valor: 400 invalid_status.
customer_idLos viajes de un cliente. Si no es un uuid: 400 invalid_id.
driver_idLos de un conductor (el driver.id del viaje, o su id en GET /team). Si no es un uuid: 400 invalid_id.
created_after, created_beforeViajes pedidos en esa fecha o después / antes (ISO 8601). Mal escrita: 400 invalid_date.
updated_afterLos que cambiaron desde esa fecha, del más viejo al más nuevo (ver sincronización incremental).
curl
# Los viajes completados de un conductor en septiembre
curl "https://api.atiendefacil.com/api/v1/rides?driver_id=a9b0c1d2-e3f4-4a5b-8c6d-7e8f9a0b1c2d&status=completed&created_after=2026-09-01T00:00:00-04:00&created_before=2026-09-30T23:59:59-04:00" \
  -H "Authorization: Bearer af_live_TU_CLAVE_AQUI"
GET/rides/{id}

Un viaje.

Los viajes se piden por WhatsApp o se crean a mano (abajo), y los mueven los conductores desde la app (ver Modo transporte); el negocio puede asignarlos, cancelarlos y cobrarlos también por la API (más abajo). Para seguirlos en vivo, usa los webhooks ride.created y ride.status_changed. El pago de un viaje se aprueba o rechaza como cualquier otro (ver pagos).

Crear un viaje

GET/vehicle-classes

Las clases de vehículo activas (id, name, kind mototaxi o taxi, description, capacity y su precio propio base_fee, per_km, min_fee, null cuando cobran la tarifa general). Su id es el vehicle_class_id de las dos llamadas de abajo.

POST/rides/quote

Busca los dos puntos en el mapa y cotiza, sin crear nada. Úsalo para enseñar la ruta antes de mandar a un conductor.

curl
curl -X POST https://api.atiendefacil.com/api/v1/rides/quote \
  -H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
  -H "Content-Type: application/json" \
  -d '{
    "pickup": { "query": "Farmacia San José, calle Comercio" },
    "dropoff": { "query": "Terminal de pasajeros" }
  }'
json
{
  "pickup": { "lat": 7.8891, "lng": -67.4735, "address": "Farmacia San José, Calle Comercio, San Fernando de Apure" },
  "dropoff": { "lat": 7.9012, "lng": -67.4581, "address": "Terminal de Pasajeros, San Fernando de Apure" },
  "distance_km": 3.4,
  "duration_minutes": 9,
  "fare": 2.5,
  "vehicles": 1,
  "total": 2.5,
  "route_url": "https://www.google.com/maps/dir/?api=1&origin=7.8891,-67.4735&destination=7.9012,-67.4581",
  "prepaid": false
}
POST/rides

Crea el viaje. Hace lo mismo que Nuevo viaje en el panel y la app: busca conductor (o espera el pago, si el negocio cobra por adelantado) y le manda la confirmación al pasajero. Responde 201 con { data: [viaje…], total, prepaid }: un viaje por vehículo.

CampoObligatorioQué es
customer_id / customer_phoneUno de los dos (solo en /rides)El pasajero. Con el teléfono, si no existe se crea; customer_name es opcional.
pickup, dropoffSíCada uno es { query } (texto a buscar en el mapa) o { lat, lng, address? } (el punto ya resuelto).
vehicle_class_idNoLa clase de vehículo. Sin ella, la única que haya o la tarifa general.
vehiclesNoDe 1 a 6. Por defecto 1.
distance_km, duration_minutesNo (solo en /rides)Los que devolvió /rides/quote: así la ruta no se mide otra vez.
noteNo (solo en /rides)Recado para el conductor.
curl
# Con los puntos y los km que devolvió /rides/quote: no se buscan ni se miden otra vez
curl -X POST https://api.atiendefacil.com/api/v1/rides \
  -H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_phone": "04140000000",
    "pickup": { "lat": 7.8891, "lng": -67.4735, "address": "Farmacia San José, Calle Comercio" },
    "dropoff": { "lat": 7.9012, "lng": -67.4581, "address": "Terminal de Pasajeros" },
    "distance_km": 3.4,
    "duration_minutes": 9,
    "note": "Lleva dos maletas"
  }'

La tarifa de la cotización no incluye el acercamiento del conductor: se suma al asignarlo. Cada búsqueda en el mapa y cada ruta descuentan saldo del negocio, salvo que la misma se haya calculado en los últimos 30 días.

ErrorCuándo
400 invalid_bodyFalta un punto, el cliente o algo tiene mal formato.
409 pickup_not_found / dropoff_not_foundEse texto no salió en el mapa.
409 pickup_out_of_area / dropoff_out_of_areaEl punto queda fuera de la zona del negocio.
409 out_of_zoneEl recorrido no cae en ninguna zona de tarifa.
409 same_pointLa recogida y el destino son el mismo punto.
409 transport_disabledEl negocio no tiene el modo transporte.
503 maps_unavailableLa búsqueda en el mapa no respondió; reintenta.

Viajes sin conductor

ride.needs_driver sale cuando ya no queda ningún conductor a quien ofrecerle el viaje: al pedirlo no había nadie cerca y libre (data.reason = "no_drivers_available"), o se le ofreció a todos los posibles y lo rechazaron, lo dejaron vencer o lo cancelaron ("no_drivers_left"). Un rechazo suelto no lo dispara: mientras quede alguien, el sistema se lo ofrece solo al siguiente.

A partir de ahí el viaje sigue en requested con driver: null y el cliente esperando: hay que asignarlo a mano, desde el panel, la app o con POST /rides/{id}/assign (abajo). Un viaje prepagado que espera el pago no lo dispara: todavía no se buscó a nadie.

Asignar, cancelar y cobrar un viaje

Lo que el negocio hace en la ficha del viaje, por las mismas funciones y con los mismos avisos al pasajero y al conductor. Todas responden el viaje y no llevan más cuerpo que el indicado.

POST/rides/{id}/assign

Le pone el viaje a un conductor: cuerpo { "driver_id": "…" } (su id en GET /fleet o GET /team). Es una asignación, no una oferta: el viaje queda accepted sin preguntarle, aunque esté fuera de turno o sin ubicación, y al pasajero le llegan los datos del conductor. Sirve también para pasárselo a otro mientras no haya empezado.

POST/rides/{id}/cancel

Cancela ese vehículo (no la solicitud entera: si pidió tres, cancela cada uno). Cuerpo opcional { "reason": "…" }, hasta 200 caracteres: se le manda al pasajero y queda en cancel_reason. Avisa al conductor si tenía uno.

POST/rides/{id}/confirm-payment

Da por bueno el pago por adelantado de un viaje en pending_payment y lo despacha: empieza a buscar conductor. Confirma la solicitud entera (todos los vehículos del mismo group.id). Si hay un comprobante por revisar, también vale aprobarlo con POST /payments/{id}/approve.

POST/rides/{id}/paid

Da por cobrado un viaje que quedó pendiente (el efectivo que recibió el conductor y no marcó). Cuerpo opcional { "payment_method": "cash" | "pago_movil" | "transferencia" }.

POST/rides/{id}/recalculate

Vuelve a medir la ruta y a cotizarla con las tarifas de hoy, para toda la solicitud. Si la tarifa cambia, se le avisa al pasajero con la cifra nueva. Mide en el mapa, así que descuenta saldo.

ErrorCuándo
404 not_foundEl viaje no existe.
404 driver_not_foundassign: ese conductor no existe o no está activo.
409 driver_busyassign: ese conductor ya lleva otro viaje.
409 already_assignedassign: el viaje ya es de ese conductor y ya lo aceptó.
409 ride_closedassign: el viaje espera el pago, ya empezó, terminó o se canceló.
409 not_cancellablecancel: ya terminó o ya estaba cancelado.
409 not_awaiting_paymentconfirm-payment: no espera un pago (ya confirmado, a crédito o cancelado).
409 not_payablepaid: ya está cobrado, o cancelado.
409 too_late / no_route / out_of_zonerecalculate: ya cobrado o cerrado, sin puntos en el mapa, o fuera de las zonas de tarifa.

Preguntas frecuentes

¿Cómo saco las citas de mañana en orden?

Filtra con from y to y ordénalas tú por starts_at: la API las devuelve de la más tardía a la más temprana.

¿Puedo agendar o cancelar citas por la API?

Sí: se agenda con POST /appointments y se mueve, confirma o cancela con PATCH /appointments/{id}. Para saber qué horas ofrecer, GET /appointments/availability.

¿No encontraste lo que buscabas?

Escríbenos y te responde una persona del equipo.