API: pedidos y pagos
Listar pedidos, cambiarles el estado, asignar repartidor y cotizar el envío; ver pagos, aprobarlos o rechazarlos, registrar cobros y reportar movimientos del banco.
Revisado el 28 de octubre de 2026
El pedido
Un pedido nace cuando el cliente lo cierra en WhatsApp o cuando alguien lo crea en el panel. Mientras el bot lo va armando es un carrito (draft) y la API no lo enseña: no aparece en las listas, GET /orders/{id} responde 404 y no dispara webhooks. La única excepción es el carrito de delivery que espera que el negocio le ponga el costo del envío: ese ya se ve (ver Cotizar el envío).
{
"id": "5b7c2e1a-3f4d-4c8e-9a6b-1d2e3f4a1b2c",
"object": "order",
"number": "4A1B2C",
"status": "confirmed",
"currency": "USD",
"subtotal": "18.00",
"delivery_fee": "2.00",
"loyalty_discount": "0.00",
"pay_method": "Pago móvil",
"pay_adjustment": "0.00",
"collect_on_delivery": null,
"total": "20.00",
"fully_paid": true,
"amount_paid": "20.00",
"balance_due": "0.00",
"exchange_rate": "36.5000",
"is_credit": false,
"fulfillment": "delivery",
"delivery": {
"address": "Av. Principal de Los Palos Grandes, casa 12",
"lat": 10.4969,
"lng": -66.8446,
"km": "3.20",
"eta_minutes": null,
"phone": null,
"assigned_to": null,
"assigned_to_id": null
},
"note": "Llamar al llegar",
"customer": {
"id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f",
"name": "María Pérez",
"phone": "584140000000"
},
"conversation_id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
"items": [
{
"name": "Hamburguesa clásica",
"quantity": 2,
"unit_price": "7.00",
"notes": "sin cebolla",
"product_id": "e5f6a7b8-c9d0-4e1f-8a2b-4c5d6e7f8a9b",
"external_id": "SKU-0101",
"barcode": "7591234500017",
"charge": null,
"options": [
{ "group": "Contorno", "name": "Papas fritas", "price": 0, "group_external_id": "GRP-CONT", "external_id": "OPT-PAPAS" }
],
"variant": null
},
{
"name": "Queso blanco (kg)",
"quantity": 0.5,
"unit_price": "8.00",
"notes": null,
"product_id": "f6a7b8c9-d0e1-4f2a-9b3c-5d6e7f8a9b0c",
"external_id": "SKU-0230",
"barcode": null,
"charge": null,
"options": [],
"variant": null
},
{
"name": "Envases",
"quantity": 2,
"unit_price": "0.50",
"notes": null,
"product_id": null,
"external_id": null,
"barcode": null,
"charge": "packaging",
"options": [],
"variant": null
}
],
"payments": [
{
"id": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
"method": "pago_movil",
"method_label": "Pago móvil",
"status": "approved",
"amount": "730.00",
"currency": "VES",
"amount_ves": "730.00",
"reference": "012345",
"group_id": null,
"received_at": "2026-09-29T18:01:05.000Z",
"resolved_by": "system",
"created_at": "2026-09-29T18:00:51.907Z"
}
],
"created_at": "2026-09-29T17:58:12.412Z",
"updated_at": "2026-09-29T18:03:40.118Z"
}| Campo | Tipo | Qué es |
|---|---|---|
id | uuid | Id del pedido. |
object | texto | Siempre order. |
number | texto | El número corto que ven el cliente y el panel (#4A1B2C): los 6 últimos caracteres del id, en mayúsculas. No es único garantizado; para identificar un pedido usa el id. |
status | texto | Ver estados. |
currency | texto | Moneda del pedido: USD, EUR o VES. |
subtotal | importe | Suma de los productos. |
delivery_fee | importe | Costo del envío ("0.00" si no hay). |
loyalty_discount | importe | Lo que rebajó el premio de la tarjeta de sellos ("0.00" si el pedido no lleva premio). Ya va restado de total. |
pay_method | texto | null | El método de pago del negocio con que paga el cliente, por su nombre tal como está en métodos de pago (Pago móvil, Zelle…). Se fija cuando el cliente lo dice en el chat, cuando llega su comprobante o cuando tu equipo lo elige en el pedido. null = todavía no se sabe. |
pay_adjustment | importe | El descuento (negativo, "-1.00") o recargo (positivo) que ese método tiene configurado, ya sumado a total. "0.00" si el método no tiene ajuste o aún no se sabe. Deja de moverse en cuanto el pedido tiene un pago aprobado. |
collect_on_delivery | texto | null | Resto contra entrega: el método en persona (punto de venta, biopago, efectivo) con que se cobra lo que falta al entregar o retirar el pedido. Con un abono aprobado, el pedido sale confirmado y balance_due es lo que se cobra en la entrega. null = no aplica. |
total | importe | Lo que paga el cliente: subtotal + delivery_fee − loyalty_discount + pay_adjustment. |
fully_paid | booleano | Si sus pagos aprobados ya cubren el total. Es la misma cuenta que pasa el pedido a confirmed; un pedido a crédito puede estar confirmed con fully_paid en false. |
amount_paid | importe | Lo que llevan aprobado sus pagos, en la moneda del pedido y sin pasar del total. Los pagos en bolívares se convierten con la tasa del pedido; en un pedido a crédito en dólares o euros, cada uno con la tasa del día en que entró. |
balance_due | importe | Lo que falta por pagar, en la moneda del pedido. 0.00 si ya está pagado. |
exchange_rate | importe | null | Bolívares por unidad de la moneda del pedido que quedaron guardados en él, con 4 decimales. Un cambio de tasa posterior no lo mueve. |
is_credit | booleano | true si el pedido va a crédito (a la cuenta del cliente). |
fulfillment | texto | null | delivery (envío), pickup (retiro en tienda) o null si no se eligió. |
delivery.address | texto | null | Dirección de entrega. |
delivery.lat | número | null | Latitud del punto de entrega, si el cliente compartió ubicación. Igual con lng. |
delivery.km | importe | null | Distancia desde el negocio, en km. |
delivery.eta_minutes | entero | null | Minutos estimados que se le dijeron al cliente al salir. |
delivery.phone | texto | null | Otro teléfono para la entrega (el portero, un familiar). null = el del cliente. |
delivery.assigned_to | texto | null | Nombre del repartidor asignado. |
delivery.assigned_to_id | uuid | null | Id del repartidor asignado (el mismo de GET /team). |
note | texto | null | Nota del pedido. |
customer | objeto | Resumen del cliente: id, name (puede ser null) y phone. |
conversation_id | uuid | null | El chat del que salió. |
items[] | lista | Una línea por producto: name, quantity (número), unit_price (importe) y notes («sin cebolla»), más qué producto del catálogo era: product_id (su id en AtiendeFácil), external_id y barcode. quantity es entero salvo en los productos que se venden fraccionados (fractional), donde puede llevar hasta 3 decimales (0.5). El nombre, el precio y los tres datos del producto son los del momento en que se anotó la línea: si el producto se renombra o se borra después, la línea no cambia. Los tres son null si la línea no casó con ningún producto del catálogo. En los pedidos anteriores a que existieran estos campos, product_id y external_id se rellenaron por nombre cuando el producto seguía en el catálogo, y barcode viene null. charge dice si la línea la puso el sistema: packaging (envases) o delivery_surcharge (recargo por delivery), que solo aparecen en el delivery y van sin producto; null = un producto pedido. options trae lo que el cliente eligió de sus grupos de opciones, [{ group, name, price, group_external_id, external_id }], con el precio de cada una ya sumado en unit_price; [] = ninguna. Los dos external_id son los que tenían el grupo y la opción en el catálogo al pedir (null si no tenían). variant es el tamaño pedido, { name, price, external_id }, o null si el producto no tiene tamaños. Las líneas anteriores al 26 de octubre de 2026 traen null en estos campos nuevos. |
payments[] | lista | Resumen de cada pago del pedido: id, method, method_label, status, amount, currency, amount_ves, reference, group_id, received_at, resolved_by y created_at, con el mismo significado que en el pago. El detalle completo, en GET /payments/{id}. |
created_at | fecha | Cuándo se creó. |
updated_at | fecha | Último cambio. |
Estados
| status | En el panel | Qué significa |
|---|---|---|
pending_payment | Esperando pago | El cliente cerró el pedido y falta que pague (o que pague el resto). |
pending_review | Revisar pago | Llegó un comprobante que el sistema no pudo confirmar solo: alguien tiene que revisarlo. |
confirmed | Confirmado | Pagado (o a crédito) y aceptado. |
preparing | Preparando | En preparación. |
ready | Listo | Listo para entregar o para que lo retiren. |
out_for_delivery | En camino | Salió con el repartidor. |
completed | Entregado | Entregado. Es final. |
cancelled | Cancelado | Cancelado. Es final. |
Listar y ver pedidos
Del más nuevo al más viejo, paginado. Filtros (todos opcionales, se combinan):
| Parámetro | Qué filtra |
|---|---|
status | Un estado de la tabla de arriba. Otro valor: 400 invalid_status. |
customer_id | Los pedidos de un cliente. Si no es un uuid: 400 invalid_id. |
created_after | Creados en esa fecha o después (ISO 8601). Mal escrita: 400 invalid_date. |
created_before | Creados en esa fecha o antes. |
fulfillment | delivery (con envío) o pickup (retiro en tienda). Otro valor se ignora. |
q | Busca por el cliente (nombre, sin importar acentos, o teléfono) o por el número del pedido. |
active | true: solo los que siguen en curso (ni entregados ni cancelados). |
is_credit | true (solo los que van a crédito) o false (solo los de contado). |
delivery_user_id | Los asignados a ese repartidor. Si no es un uuid: 400 invalid_id. |
updated_after | Los que cambiaron en esa fecha o después, del cambio más viejo al más nuevo. Ver sincronización incremental. |
page, limit | Ver paginación. |
curl "https://api.atiendefacil.com/api/v1/orders?status=pending_review&limit=50" \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI"Un pedido. 404 not_found si no existe, es de otro negocio o es un carrito (salvo el que espera la cotización del envío, que sí sale, en draft).
Crear un pedido
Carga un pedido que no entró por WhatsApp (el del teléfono, el del mostrador, el de tu punto de venta). Hace lo mismo que Nuevo pedido en el panel y la app: aparta existencias, avisa al dueño y queda en pending_payment. Responde 201 con el pedido.
| Campo | Obligatorio | Qué es |
|---|---|---|
customer_id / customer_phone | Uno de los dos | El cliente. Con customer_phone, si no existe se crea; customer_name es opcional. |
items | Sí | De 1 a 50 líneas { product_id, variant?, options?, quantity, notes? }. El product_id es el id del producto (ver catálogo); el precio es el de hoy, no se manda. Si el producto tiene tamaños, variant es el nombre exacto de uno («Familiar»): sin él responde 400 con los tamaños que hay. options es lo elegido de sus grupos, [{ group?, name }]: se exigen los obligatorios, no más del máximo de cada grupo, y cada opción suma su precio. |
fulfillment | Sí | pickup o delivery. Tiene que ser una modalidad que el negocio ofrezca. |
delivery_address | En delivery | Dirección escrita. |
delivery_fee | No | Costo del envío, en la moneda base. Por defecto 0; aquí no se calcula solo. |
note | No | Nota del pedido. |
notify_customer | No | Por defecto true: al cliente le llega el pedido con el total y cómo pagar. En el canal oficial, fuera de la ventana de 24 h sale dentro de una plantilla; si no se le puede escribir, el pedido se crea igual. |
curl -X POST https://api.atiendefacil.com/api/v1/orders \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{
"customer_phone": "04140000000",
"customer_name": "María González",
"items": [
{ "product_id": "0c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f", "quantity": 2, "notes": "Sin cebolla",
"options": [{ "group": "Contorno", "name": "Papas fritas" }] },
{ "product_id": "1d2e3f4a-5b6c-4d7e-8f9a-0b1c2d3e4f5a", "variant": "Familiar", "quantity": 1 }
],
"fulfillment": "delivery",
"delivery_address": "Av. Bolívar, edificio Roma, piso 2",
"delivery_fee": 3,
"notify_customer": true
}'Errores: 400 invalid_body (falta algo o tiene mal formato), 400 order_rejected (un producto no existe, no está disponible, no hay existencias o la modalidad no se ofrece; el mensaje dice cuál) y 404 not_found (ese customer_id no existe).
Cambiar el estado de un pedido
Hace lo mismo que los botones de estado del panel y de la app. Cuerpo:
| Campo | Obligatorio | Qué es |
|---|---|---|
status | Sí | Uno de confirmed, preparing, ready, out_for_delivery, completed, cancelled. |
eta_minutes | No | Entero de 1 a 300. Solo se usa con out_for_delivery: se guarda en el pedido y va en el aviso al cliente («Tiempo estimado de entrega: 25 min»). Con otro estado se ignora. |
delivery_user_id | No | Id del repartidor que lleva el pedido. Solo va con out_for_delivery (con otro estado: 400 invalid_body). Tiene que ser alguien del equipo con rol de repartidor y activo; si no, 400 delivery_user_invalid. Los ids salen de GET /team. |
curl -X POST https://api.atiendefacil.com/api/v1/orders/5b7c2e1a-3f4d-4c8e-9a6b-1d2e3f4a1b2c/status \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{ "status": "out_for_delivery", "eta_minutes": 25 }'Asignar el repartidor
Como en el panel, el repartidor se asigna al mandar el pedido en camino: añade delivery_user_id al pasar a out_for_delivery. Si no lo mandas, se queda el que ya tuviera el pedido. El repartidor ve el pedido en «Mis entregas» de su app y, conectado por QR, le llega el aviso por WhatsApp.
# Mandarlo en camino con un repartidor (su id sale de GET /team)
curl -X POST https://api.atiendefacil.com/api/v1/orders/5b7c2e1a-3f4d-4c8e-9a6b-1d2e3f4a1b2c/status \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{
"status": "out_for_delivery",
"eta_minutes": 25,
"delivery_user_id": "b3c4d5e6-f7a8-4b9c-8d0e-1f2a3b4c5d6e"
}'Sale order.status_changed y, si el repartidor es nuevo, order.assigned (con assigned_to y previous_assigned_to). Cambiar el repartidor de un pedido que ya va en camino no se puede por la API: se hace desde el panel o la app.
Responde 200 con el pedido ya actualizado. Mira su status: al confirmar con un pago parcial, el pedido se queda en pending_payment (ver abajo).
Transiciones permitidas
Desde cada estado solo se puede ir a algunos. Lo demás responde 409 invalid_transition. Ojo: de confirmed o preparing sí se puede saltar a completed, pero es un cierre interno y al cliente no se le avisa. Para que reciba «¡Pedido entregado!», pasa antes por ready (retiro en tienda) o out_for_delivery (envío).
| Desde | Puede pasar a |
|---|---|
pending_payment | confirmedcancelled |
pending_review | confirmedcancelled |
confirmed | preparingreadyout_for_deliverycompletedcancelled |
preparing | readyout_for_deliverycompletedcancelled |
ready | out_for_deliverycompletedcancelled |
out_for_delivery | completedcancelled |
completed | Ninguno: es un estado final. |
cancelled | Ninguno: es un estado final. |
Qué pasa al cambiarlo
| A | Efectos |
|---|---|
confirmed | Si el pedido esperaba pago, aprueba todos sus comprobantes pendientes o por revisar (quedan firmados por tu clave). Si con eso lo pagado cubre el total, el pedido se confirma y al cliente le llega «¡Pago confirmado! Tu pedido fue aceptado…». Si no lo cubre, el pedido sigue en pending_payment y al cliente se le cobra el resto. Sin ningún pago que aprobar y sin el total cubierto: 409 no_payment (el mensaje dice cuánto falta) y no se toca nada. |
preparing | Aviso al cliente: «Estamos preparando tu pedido». |
ready | Aviso al cliente: listo para retirar en la tienda (retiro) o listo (envío). |
out_for_delivery | Aviso «Tu pedido va en camino», con los minutos si mandaste eta_minutes. En pedidos con envío, además: el enlace de seguimiento si el negocio lo tiene encendido, lo que falte por pagar contra entrega, y el aviso por WhatsApp al repartidor asignado (en el canal oficial, fuera de las 24 horas sale como plantilla si está aprobada). El repartidor es el de delivery_user_id o, si no lo mandas, el que ya tenía. |
completed | Desde ready u out_for_delivery, aviso al cliente: «¡Pedido entregado!». Desde confirmed o preparing, sin aviso. |
cancelled | Aviso al cliente: «Tu pedido fue cancelado». Si esperaba pago, el comprobante pendiente se rechaza. |
En todos los casos, además:
- Se imprime la comanda o el ticket si el negocio lo tiene configurado para ese estado (ver Impresión de comandas).
- El panel y la app lo muestran al instante, y sale el webhook
order.status_changed(másorder.assignedsi cambió el repartidor). - Queda en la auditoría: «cambió el pedido a «preparing» desde la API».
- El aviso al cliente sale por el WhatsApp del negocio y queda en su chat. En el canal oficial, si han pasado más de 24 horas desde su último mensaje, sale dentro de la plantilla de avisos al cliente («Tienes una novedad de (negocio): …»), que Meta cobra a la cuenta de WhatsApp Business del negocio. Si esa plantilla aún no está aprobada, el estado cambia igual, pero el aviso queda en el chat marcado como no enviado. La respuesta de la API no lo dice.
Errores
| Respuesta | Cuándo |
|---|---|
| 400 invalid_body | Falta status, trae un valor no permitido, eta_minutes está fuera de 1–300 o mandaste delivery_user_id con un estado que no es out_for_delivery. |
| 400 delivery_user_invalid | El delivery_user_id no es un repartidor activo del negocio. |
| 404 not_found | El pedido no existe, es de otro negocio o es un carrito. |
| 409 invalid_transition | El pedido no puede pasar de su estado actual al que pides. |
| 409 no_payment | Pediste confirmed, el pedido espera pago y no hay nada que aprobar. |
Reimprimir un pedido
Manda el pedido otra vez a las impresoras del negocio, como el botón de reimprimir del panel. Sale marcado como copia. Cuerpo opcional { "kind": "kitchen" }: kitchen (la comanda, por defecto) o customer (el ticket del cliente). Responde { "object": "print_request", "order_id", "kind", "printers" }, donde printers es a cuántas impresoras se mandó.
Errores: 404 not_found (el pedido) y 409 no_printers (el negocio no tiene ninguna impresora encendida para ese tipo de ticket; ver Impresión de comandas). La impresión normal, la de cuando el pedido entra, sigue siendo automática: esto es solo para pedir una copia.
Cotizar el envío
Si el negocio cotiza el delivery a mano (ver Delivery y repartidores), cuando un cliente pide con envío el bot le dice que están calculando el costo y espera. En ese momento sale el webhook order.delivery_quote_needed: su data.object es el pedido, todavía en draft y sin costo de envío, y data.conversation_id es el chat del cliente.
Le pone el costo del envío. Cuerpo: { fee }, un número de 0 en adelante en la moneda del negocio (base_currency de GET /me); se redondea a dos decimales.
curl -X POST https://api.atiendefacil.com/api/v1/orders/5b7c2e1a-3f4d-4c8e-9a6b-1d2e3f4a1b2c/delivery-quote \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{ "fee": 2.5 }'| Si el pedido… | Qué pasa |
|---|---|
| Esperaba la cotización | Hace lo mismo que cotizar desde el panel: fija el envío, coloca el pedido (pasa a pending_payment, y sale order.created), le manda al cliente el total con los datos de pago y el bot vuelve a atenderlo. |
Ya tenía precio y espera el pago (pending_payment o pending_review) | Corrige el envío: recalcula el total, deja el pedido en pending_payment, rechaza los comprobantes pendientes (ya no cuadran con el total nuevo) y le manda al cliente el importe nuevo. |
| Es un carrito que el cliente todavía está armando (no espera cotización) | 409 not_quotable. No se toca nada. |
| No es de delivery, o ya está pagado o en marcha | 409 not_quotable. No se toca nada. |
Responde 200 con el pedido actualizado. Otros errores: 400 invalid_body (falta fee o es negativo) y 404 not_found. Mientras la cotización está pendiente, la conversación trae awaiting_delivery_quote: true y no se puede devolver al bot con /resume: se resuelve cotizando.
El pago
Un pago es un comprobante del cliente (la foto del pago móvil o la transferencia que mandó por WhatsApp), un cobro registrado a mano o por la API, o el pago de una cita (la seña) o de un viaje. Va enlazado a uno de los tres: order_id, appointment_id o ride_id.
{
"id": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
"object": "payment",
"status": "approved",
"method": "pago_movil",
"method_label": "Pago móvil",
"amount": "730.00",
"currency": "VES",
"amount_ves": "730.00",
"reference": "012345",
"group_id": null,
"received_at": "2026-09-29T18:01:05.000Z",
"order_id": "5b7c2e1a-3f4d-4c8e-9a6b-1d2e3f4a1b2c",
"appointment_id": null,
"ride_id": null,
"conversation_id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
"customer": {
"id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f",
"name": "María Pérez",
"phone": "584140000000"
},
"resolved_at": "2026-09-29T18:03:40.101Z",
"resolved_by": "system",
"created_at": "2026-09-29T18:00:51.907Z",
"updated_at": "2026-09-29T18:03:40.118Z"
}| Campo | Tipo | Qué es |
|---|---|---|
id | uuid | Id del pago. |
object | texto | Siempre payment. |
status | texto | Ver estados. |
method | texto | pago_movil (pago móvil), transferencia (transferencia), binance (binance), otro (otro método), cash (efectivo). otro es un método digital que no es banco venezolano ni Binance (Zelle, PayPal, Zinli…): su nombre va en method_label. Trata cualquier valor que no conozcas como un método más: la lista puede crecer. |
method_label | texto | null | El nombre del método del negocio con que pagó el cliente, tal como lo tiene puesto el negocio («Zelle», «Pago móvil»). null en los pagos de antes y cuando no se supo. |
amount | importe | null | El monto, en su moneda. null si el comprobante no lo trae (hay que darlo al aprobar). |
currency | texto | Moneda del monto. |
amount_ves | importe | null | Lo que vale en bolívares, calculado al aprobarlo. Si va en la misma moneda que su pedido, con la tasa del pedido (así un pago exacto lo salda); si va en otra moneda, con la tasa del día en que llegó el dinero. Es lo que suma contra el pedido. En un pedido a crédito en dólares o euros todo va con la tasa del día en que llegó el dinero: el fiado se salda a la tasa del día del pago. |
reference | texto | null | Referencia bancaria (o la que mandaste al registrar el cobro). |
group_id | texto | null | Los pagos que salieron del mismo dinero comparten este valor: un cobro con varios métodos, o un pago repartido entre varios pedidos del cliente (en el panel, la insignia «Pago repartido»). null si el pago va solo. |
received_at | fecha | null | Cuándo llegó el dinero de verdad (del banco, de la caja o lo que indicaste). |
order_id | uuid | null | El pedido que paga, si es de un pedido. |
appointment_id | uuid | null | La cita que paga (una seña), si es de una cita. |
ride_id | uuid | null | El viaje que paga, si es de un viaje. |
conversation_id | uuid | null | El chat del pedido o del viaje que paga, para escribirle al cliente sin pedir antes el pedido. En la seña de una cita, el chat del cliente de esa cita. null en lo creado sin chat. |
customer | objeto | null | Resumen del cliente del pedido, la cita o el viaje. |
resolved_at | fecha | null | Cuándo se aprobó o rechazó. |
resolved_by | texto | null | Quién lo resolvió: user (alguien del equipo), bot (la IA validó el comprobante) o system (el cruce con el banco o una integración por la API). Lo que haces con tu clave queda como system, y en el panel se ve «API · nombre de la clave». |
created_at | fecha | Cuándo se creó. |
updated_at | fecha | Último cambio. |
Estados de un pago
| status | En el panel | Qué significa |
|---|---|---|
pending | Por revisar | Llegó y espera validación: la automática (IA o banco) o la de alguien del equipo. |
review | Por revisar | El sistema no pudo confirmarlo solo (no leyó el monto, pasa de lo que debe el cliente, falta un dato): lo decide una persona. |
approved | Aprobado | Aprobado. Cuenta para el pedido, la cita o el viaje. |
rejected | Rechazado | Rechazado. No cuenta. |
Cuando un pago entra en review (porque llegó así o porque pasó a ese estado) sale el webhook payment.needs_review, además del de siempre: es la señal de que alguien tiene que aprobarlo o rechazarlo (abajo). Los pagos en pending no lo disparan: esos todavía los puede confirmar la IA o el cruce con el banco.
Repartidores: asignar y ver la flota en vivo
| Petición | Qué hace |
|---|---|
POST /orders/{id}/assign | Pone, cambia o quita el repartidor de un pedido sin moverlo de estado. Cuerpo: delivery_user_id (un id de GET /team?role=delivery) o null para quitarlo. Vale con el pedido confirmado, en preparación, listo o en camino; a uno en camino no se le puede quitar, solo cambiar. Devuelve el pedido. Al repartidor que lo recibe ya en camino le llega el aviso de entrega. |
GET /fleet | La flota en vivo: origin (dónde está el negocio) y data, un elemento por repartidor o conductor activo con name, phone, on_shift, location (lat, lng, updated_at; null si nunca reportó), active_orders y stops, las entregas que lleva encima con su dirección y coordenadas. |
La posición la reporta la app del repartidor cada pocos segundos mientras tiene el turno abierto. No hay aviso por webhook de cada movimiento: para un mapa en vivo, consulta GET /fleet cada 5 a 10 segundos mientras el mapa esté a la vista, y mira updated_at para saber si una posición es reciente. También se puede asignar al mandar el pedido en camino, con delivery_user_id en POST /orders/{id}/status.
Listar y ver pagos
Del más nuevo al más viejo, paginado. Filtros (todos opcionales, se combinan):
| Parámetro | Qué filtra |
|---|---|
status | Uno de los cuatro estados de arriba. Otro valor: 400 invalid_status. |
order_id, appointment_id, ride_id | Los pagos de ese pedido, esa cita o ese viaje. Si no es un uuid: 400 invalid_id. |
method | pago_movil, transferencia o cash. Otro valor se ignora. |
created_after, created_before | Creados en esa fecha o después / antes (ISO 8601). Mal escrita: 400 invalid_date. |
updated_after | Los que cambiaron desde esa fecha, del más viejo al más nuevo (ver sincronización incremental). |
Un pago, de pedido, cita o viaje.
Aprobar o rechazar un comprobante
Hacen lo mismo que Aprobar y Rechazar en la sección Pagos del panel y de la app. Solo sirven con pagos en pending o review; con uno ya aprobado o rechazado responden 409 already_resolved. Responden 200 con el pago actualizado.
Al aprobar puedes mandar lo que de verdad entró, si no coincide con el comprobante (todo opcional; el cuerpo puede ir vacío):
| Campo | Qué es |
|---|---|
amount | Monto recibido, mayor que 0. Reemplaza el del comprobante. |
currency | VES, USD o EUR. Si no va, se mantiene la del pago. |
received_at | Cuándo llegó el dinero (ISO 8601 con zona). Decide con qué tasa del día se pasa a bolívares. |
# Aprobar tal cual (el comprobante ya trae el monto)
curl -X POST https://api.atiendefacil.com/api/v1/payments/7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d/approve \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI"
# Aprobar corrigiendo lo que de verdad entró
curl -X POST https://api.atiendefacil.com/api/v1/payments/7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d/approve \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{ "amount": 730, "currency": "VES", "received_at": "2026-09-29T14:01:05-04:00" }'Qué pasa al aprobar
- Pago de un pedido: se calcula su valor en bolívares con la tasa del día en que llegó. Si lo aprobado cubre el total, el pedido pasa a
confirmedy al cliente le llega «¡Pago confirmado! Tu pedido quedó confirmado»; si no, sigue esperando y se le dice cuánto falta. Un pedido a crédito no cambia de estado, y uno que ya avanzó (preparándose, en camino) no vuelve a esperar el pago. Si el comprobante no trae monto y no mandasamount:400 needs_amount. - Si sobra dinero: igual que en el panel, lo que pasa de lo que debe el pedido se aplica a los otros pedidos del mismo cliente que deben (esperando pago o a crédito con saldo), del más viejo al más nuevo, con pagos nuevos que comparten
group_id. Lo que quede después es vuelto del pedido original. - Seña de una cita: si lo aprobado cubre la seña, la cita pasa a
confirmedy se le avisa al cliente; si no, sigue enrequestedy se le dice cuánto falta. - Pago de un viaje: se despacha la solicitud a los conductores (o, si el viaje ya iba en camino, se le avisa al conductor de que no cobre). Aquí
amount,currencyyreceived_atno se usan.
Qué pasa al rechazar
- Pedido con otros pagos (aprobados o pendientes): no se cancela. Vuelve a
pending_payment(opending_reviewsi le queda otro por revisar;confirmedsi con los demás ya está cubierto) y al cliente se le dice cuánto falta. - Pedido a crédito o que ya avanzó: no cambia de estado; al cliente le llega «Uno de tus pagos no pudo ser aprobado…».
- Seña de una cita: la cita queda
requested(solicitada) para que el negocio decida, y se le avisa al cliente. - Pago de un viaje: el viaje sigue esperando el pago y se le dice al pasajero.
Salen los webhooks payment.status_changed y, si el pedido cambia, order.status_changed, igual que desde el panel.
Registrar un cobro
Para el dinero que entró por fuera del chat: tu caja, tu punto de venta, tu tienda online. Es el botón Registrar cobro del panel. El pago nace ya aprobado; se permiten abonos parciales.
| Campo | Obligatorio | Qué es |
|---|---|---|
amount | Sí | Monto, mayor que 0. |
currency | Sí | VES, USD o EUR. |
method | Sí | cash, pago_movil o transferencia. |
reference | No | Hasta 120 caracteres: tu número de operación o de ticket. |
received_at | No | Cuándo entró el dinero (ISO 8601 con zona). Si no va, ahora. |
notify_customer | No | Avisarle al cliente por WhatsApp. Si no lo mandas, se le avisa solo cuando el cobro mueve el pedido (lo confirma o le cobra el resto). false: nunca; true: siempre. |
curl -X POST https://api.atiendefacil.com/api/v1/orders/5b7c2e1a-3f4d-4c8e-9a6b-1d2e3f4a1b2c/payments \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{
"amount": 10,
"currency": "USD",
"method": "cash",
"reference": "CAJA-1-000318"
}'Responde 201 con el pago creado y cómo quedó el pedido. order_remaining es lo que falta en la moneda del pedido y, a diferencia del resto de importes, viene como número.
{
"payment": {
"id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
"object": "payment",
"status": "approved",
"method": "cash",
"amount": "10.00",
"currency": "USD",
"amount_ves": "365.00",
"reference": "CAJA-1-000318",
"received_at": "2026-09-29T18:10:00.000Z",
"order_id": "5b7c2e1a-3f4d-4c8e-9a6b-1d2e3f4a1b2c",
…
},
"order_fully_paid": false,
"order_remaining": 10
}- El valor en bolívares se congela con la tasa del día en que entró el dinero, no la de hoy.
- Si el pedido esperaba el pago (
pending_paymentopending_review) y con esto queda cubierto, pasa aconfirmedy se imprime la comanda si toca. Si no queda cubierto, sigue esperando el resto. - Un pedido que ya avanzó (preparándose, en camino, entregado) o que va a crédito no cambia de estado: el cobro solo baja lo que se debe.
- Errores:
404 not_found(no existe o es un carrito),409 order_cancelled,400 invalid_body.
Cobrar varios pedidos o con varios métodos
Un cobro con varios métodos (una parte en efectivo y otra por pago móvil) y/o sobre varios pedidos del mismo cliente. Es lo que hace Registrar cobro en el panel con varias líneas o con «Cobrar también otros pedidos de este cliente». Los pagos nacen ya aprobados.
| Campo | Obligatorio | Qué es |
|---|---|---|
order_ids | Sí | De 1 a 20 ids de pedidos, todos del mismo cliente. El dinero se reparte en el orden de creación de los pedidos, del más viejo al más nuevo. |
lines | Sí | De 1 a 6 líneas, cada una { amount, currency, method, reference? }: amount mayor que 0; currency VES, USD o EUR; method cash, pago_movil o transferencia; reference hasta 120 caracteres. |
received_at | No | Cuándo entró el dinero (ISO 8601 con zona). Si no va, ahora. |
notify_customer | No | Igual que en POST /orders/{id}/payments. |
curl -X POST https://api.atiendefacil.com/api/v1/payments/collect \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{
"order_ids": [
"5b7c2e1a-3f4d-4c8e-9a6b-1d2e3f4a1b2c",
"8d9e0f1a-2b3c-4d5e-8f6a-7b8c9d0e1f2a"
],
"lines": [
{ "amount": 10, "currency": "USD", "method": "cash" },
{ "amount": 500, "currency": "VES", "method": "pago_movil", "reference": "004512" }
]
}'Responde 201 con payments (los pagos creados, uno por pedido y por método, todos con el mismo group_id), group_id y orders: por cada pedido, order_id, fully_paid, remaining (lo que falta, en su moneda) y change_due (vuelto). remaining y change_due vienen como número.
{
"payments": [
{ "id": "3c4d5e6f-…", "object": "payment", "status": "approved", "method": "cash", "order_id": "5b7c2e1a-…", "group_id": "9f8e7d6c-…", … },
{ "id": "4d5e6f7a-…", "object": "payment", "status": "approved", "method": "pago_movil", "order_id": "8d9e0f1a-…", "group_id": "9f8e7d6c-…", … }
],
"group_id": "9f8e7d6c-5b4a-4392-8a1b-0c9d8e7f6a5b",
"orders": [
{ "order_id": "5b7c2e1a-3f4d-4c8e-9a6b-1d2e3f4a1b2c", "fully_paid": true, "remaining": 0, "change_due": 0 },
{ "order_id": "8d9e0f1a-2b3c-4d5e-8f6a-7b8c9d0e1f2a", "fully_paid": false, "remaining": 3, "change_due": 0 }
]
}- Cada pedido se llena hasta lo que debe y se pasa al siguiente; lo que sobre después del último queda como vuelto en ese último.
- Cada pedido cambia de estado igual que con un cobro normal (ver arriba).
- Errores:
400 invalid_body,400 mixed_customers(pedidos de clientes distintos),404 not_found(algún pedido no existe o es un carrito),409 order_cancelled(alguno está cancelado). Si falla, no se crea ningún pago.
Reportar un movimiento del banco
Si tu sistema ve los ingresos de la cuenta del banco (tu banca en línea, tu ERP), mándalos aquí. Se concilian igual que los avisos del banco que ya recibe AtiendeFácil (ver Conexión con el banco): si casan con un comprobante pendiente de un cliente, el pedido o la cita se confirman solos.
| Campo | Obligatorio | Qué es |
|---|---|---|
amount | Sí | Monto en bolívares, mayor que 0. |
reference | Sí | Referencia de la operación (1 a 120 caracteres). Es lo que se cruza con el comprobante. |
sender_phone | No | Teléfono de quien pagó (pago móvil). Se quedan solo los dígitos. |
bank | No | Nombre del banco de origen, hasta 80 caracteres. |
description | No | Texto libre, hasta 1.000 caracteres, para que el dueño lo reconozca. |
curl -X POST https://api.atiendefacil.com/api/v1/bank-movements \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{
"amount": 730.00,
"reference": "004512345678",
"sender_phone": "04140000000",
"bank": "Banesco"
}'{
"id": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e",
"object": "bank_movement",
"status": "matched"
}| status | HTTP | Qué pasó |
|---|---|---|
matched | 201 | Casó con un comprobante pendiente: el pago se aprobó y el pedido o la cita se confirmaron. |
unmatched | 201 | Quedó guardado sin casar. Cuando llegue el comprobante del cliente con esa referencia, se cruzará solo. |
duplicate | 200 | Ya habías reportado esa referencia: no se hace nada. Puedes reintentar sin miedo a duplicar. |
Métodos de pago del negocio
Son las formas de pago que el asistente le ofrece al cliente y los datos que le manda para pagar: los mismos de Ajustes → Pagos, en el panel y en la app.
| Petición | Qué hace |
|---|---|
GET /payment-methods | Todos los métodos del negocio: id, external_id, kind, label, enabled, fields (cada uno con label y value) y currency (VES o USD: la moneda en que se cobra por ese método). |
PUT /payment-methods/sync | Publica los métodos de tu sistema. Cuerpo: methods, una lista en la que cada uno lleva external_id, label, fields (de 1 a 8; solo el efectivo puede ir sin ninguno) y, opcionales, kind y currency (VES o USD, solo en los personalizados; sin ella se deduce del nombre: «Zelle» → USD). Devuelve cómo quedaron todos. |
- La lista es completa: un método publicado por tu sistema que deje de venir se quita. Una lista vacía quita todos los tuyos.
- Los métodos que el dueño cargó a mano (
external_id: null) no se tocan. kind: "pago_movil"ykind: "transferencia"son los que el sistema valida solo con el comprobante. No se crean otros: se rellenan esos dos, y sus campos tienen que llamarse exactamente Banco, Teléfono y Cédula/RIF (pago móvil) o Banco, N.º de cuenta, Titular y Cédula/RIF (transferencia). Si dejas de mandarlo, se apaga. Un RIF nuevo o cambiado tiene que traer un dígito verificador que cuadre, y la cuenta tiene que ser de 20 dígitos y empezar por el código del banco; si no, respondeinvalid_bodycon el motivo.kind: "binance"rellena el método Binance del negocio, que también existe siempre. Sus campos se llaman Pay ID, Correo de Binance y Usuario; basta mandar el Pay ID o el correo. Se valida solo si el negocio enciende la validación con Binance en el panel. EnGET /payment-methodssale con esekind.- Sin
kind(o concustom) el método es libre: Zelle, efectivo, tarjeta… con los campos que quieras.
| Error | Cuándo |
|---|---|
| 400 invalid_body | Falta un campo, un external_id viene repetido, hay dos métodos del mismo tipo fijo, a pago móvil o transferencia les falta un campo con su nombre exacto, o Binance no trae ni Pay ID ni correo. |
| 409 duplicate_name | Dos métodos quedarían con el mismo nombre (por ejemplo, uno tuyo y uno cargado a mano). |
| 409 payment_methods_full | El negocio quedaría con más métodos de los que admite. |
Preguntas frecuentes
¿Cómo me entero de los pedidos nuevos sin consultar a cada rato?
Con el webhook order.created (y payment.created para los comprobantes). Si además quieres enterarte de lo que espera a alguien, suscríbete a payment.needs_review y order.delivery_quote_needed. Ver Webhooks.
¿Cómo sé qué pedidos lleva cada repartidor?
GET /orders?delivery_user_id=…&status=out_for_delivery. El id de cada repartidor sale de GET /team?role=delivery, y dentro del pedido viene en delivery.assigned_to_id.
¿Confirmar un pedido por la API o aprobar su pago da lo mismo?
Casi. POST /orders/{id}/status con confirmed aprueba todos los comprobantes pendientes del pedido tal como vienen. POST /payments/{id}/approve aprueba ese pago en concreto y te deja corregir monto, moneda y fecha. Si necesitas corregir alguno, usa el segundo.
¿Puedo cambiar los productos o el total de un pedido?
Los productos, no: se cambian desde el panel o la app. El costo del envío sí, mientras el pedido espera el pago, con POST /orders/{id}/delivery-quote, y el total se recalcula solo.
