Desarrolladores · versión 1.1.0
API de integraciones de Punto
Con esta API, el sistema de un comercio que usa Punto (su tienda online, su app de pedidos, su sistema de gestión) le manda a Punto sus clientes y sus pedidos, y Punto hace el resto: descuenta stock, calcula los impuestos, numera el comprobante, emite la factura electrónica cuando corresponde y lo suma a los reportes, igual que si la venta se hubiera hecho en el local.
Los pedidos entran de dos maneras: como venta (un pedido cobrado, que se factura en el acto) o como orden (un pedido que el comercio prepara y entrega, y que se factura después).
Si es tu primera vez, empezá por Antes de empezar y seguí con la Guía de inicio.
Dirección base de la API
export PUNTO_API="https://api.punto.la"
export PUNTO_KEY="<tu key de integración>"Antes de empezar
Esta API conecta el sistema de un comercio que usa Punto —su tienda online, su app de pedidos, su sistema de gestión— con Punto. La programás vos, con la autorización del comercio, y necesitás tres cosas que el comercio prepara desde su panel de Punto:
- La API de integraciones activa. Es un complemento del plan del comercio. Si al emitir la key la opción Integración de ventas aparece apagada, el comercio tiene que pedirle a Punto que la active. Sin el complemento, todas las llamadas responden
403conreason: addon_api_required. - Una caja para tu sistema. Cada venta y cada orden se registran contra una caja de Punto. El comercio la crea en Configuración › Sucursales, como cualquier otra caja, y esa caja decide lo fiscal: si tiene cargados el timbrado y el punto de expedición, las ventas salen con factura electrónica; si no, se registran igual, sin factura. Pedile una caja dedicada a tu sistema, sin ningún dispositivo conectado: si en la misma caja también vende una persona, los dos numeran comprobantes de la misma serie.
- Una key de integración, que el comercio emite y te pasa. Ver Autenticación.
Todas las direcciones de esta referencia son relativas a la dirección base de la API, que en los ejemplos aparece como $PUNTO_API. Los cuerpos y las respuestas son JSON en UTF-8, y las fechas, en la zona horaria del comercio.
Guía de inicio
El recorrido completo, de la key a la factura. Cada paso tiene su sección en la referencia, con todos los campos.
1. Probá la key con las cajas disponibles.
curl "$PUNTO_API/v1/integrations/registers" \
-H "Authorization: Bearer $PUNTO_KEY"
La respuesta lista las cajas que tu key puede usar. Guardá el registerId de la que el comercio preparó para tu sistema, que tiene que estar con available: true: una caja que usa un dispositivo del comercio no se puede usar desde tu sistema, y la que empiece a usar tu sistema deja de poder usarse en el local; si su electronicInvoice es true, las ventas que registres ahí salen con factura electrónica. Si recibís 401, la key está mal copiada; si recibís 403, mirá error.details.reason en Errores.
2. Traé el catálogo y los medios de pago. Cada línea de un pedido nombra un artículo de Punto por su itemId (o por su sku), y cada venta, el medio de pago por su id:
curl "$PUNTO_API/v1/items?after=&limit=200" -H "Authorization: Bearer $PUNTO_KEY"
curl "$PUNTO_API/v1/payment-methods" -H "Authorization: Bearer $PUNTO_KEY"
3. Creá el cliente. Con el identificador del cliente en tu sistema (externalId). Si el comercio ya lo tenía cargado con el mismo documento o teléfono, Punto te devuelve ese cliente en vez de duplicarlo:
curl -X POST "$PUNTO_API/v1/integrations/customers" \
-H "Authorization: Bearer $PUNTO_KEY" \
-H "Content-Type: application/json" \
-d '{ "externalId": "cliente-4812", "name": "Ana Gómez", "document": "4567890", "phone": "+595981000111" }'
4. Cargá la orden. Un pedido que el comercio tiene que preparar y entregar entra como orden. Nombrá al comprador con el mismo externalId:
curl -X POST "$PUNTO_API/v1/integrations/orders" \
-H "Authorization: Bearer $PUNTO_KEY" \
-H "Content-Type: application/json" \
-d '{
"orderId": "WEB-10246",
"registerId": "81c541da-640e-4891-a1a0-b32841e64c75",
"fulfillment": "takeaway",
"items": [ { "itemId": "10223f3b-2e3d-4339-8496-9f288d8be65b", "quantity": 2, "unitPrice": 26000 } ],
"total": 52000,
"customer": { "externalId": "cliente-4812" }
}'
La orden entra pendiente: alguien del comercio la acepta o la rechaza. Consultá GET /v1/integrations/orders?orderId=WEB-10246 y mirá acceptance.status.
5. Facturala. Cuando el comprador pagó y la orden está aceptada, registrá la venta con fromOrderId:
curl -X POST "$PUNTO_API/v1/integrations/sales" \
-H "Authorization: Bearer $PUNTO_KEY" \
-H "Content-Type: application/json" \
-d '{
"orderId": "WEB-10246",
"fromOrderId": "WEB-10246",
"registerId": "81c541da-640e-4891-a1a0-b32841e64c75",
"items": [ { "itemId": "10223f3b-2e3d-4339-8496-9f288d8be65b", "quantity": 2, "unitPrice": 26000 } ],
"total": 52000,
"payment": { "method": "e7a1c3d5-2b49-4f60-8d17-5c9e0a3b6f24", "reference": "psp_7f3a91" }
}'
Si tu sistema cobra y entrega en el momento, salteá la orden: mandá directo la venta, sin fromOrderId.
6. Consultá la factura y el KuDE. La factura electrónica se valida en segundo plano. Consultá la venta cada 30 segundos hasta que einvoice.sifenVerdict deje de ser pending, y mostrale al comprador el link einvoice.portalUrl, donde descarga su factura:
curl "$PUNTO_API/v1/integrations/sales?orderId=WEB-10246" -H "Authorization: Bearer $PUNTO_KEY"
Autenticación
Cada llamada lleva la key en el encabezado Authorization:
Authorization: Bearer <tu key>
Cómo se obtiene. La emite el comercio desde su panel de Punto, en Configuración › Keys de integración → Nueva key, eligiendo Integración de ventas en Qué puede hacer. Se muestra una sola vez. Una key emitida con otra opción de esa pantalla no sirve para esta API: responde 403 con reason: api_key_scope_missing, y no se puede convertir; el comercio emite una nueva.
Guardala en tu servidor. Nunca en el navegador, en una app que se instala en el teléfono ni en un repositorio de código: quien tenga la key puede registrar ventas a nombre del comercio.
Una key puede lo mismo que el usuario que la emitió. Si ese usuario no tiene permiso para vender, las llamadas responden 403; si solo tiene acceso a algunas sucursales, la key solo ve esas sucursales y sus cajas. Pedile al comercio que la emita con un usuario que tenga permiso para vender y acceso a la sucursal de tu caja.
Cortar el acceso. El comercio revoca la key desde la misma pantalla y deja de funcionar en el acto, sin tocar lo que ya se registró. Si el comercio da de baja el complemento de integraciones, todas sus keys dejan de responder.
Límites de uso
Cada key tiene dos límites, que se cuentan por key:
- 60 llamadas por minuto.
- 5.000 llamadas por día.
Al pasarte, la llamada responde 429 y no se procesa. Esperá un minuto antes de reintentar y espaciá los reintentos (por ejemplo, 1, 2, 4 y 8 segundos entre intentos). Para seguir el estado de una factura o de una orden alcanza con una consulta cada 30 segundos.
Otros topes: hasta 200 líneas por pedido, 200 artículos por consulta de stock y 100 clientes por página.
Errores
Toda respuesta tiene la misma forma. Si salió bien:
{ "ok": true, "data": { ... } }
Si no:
{
"ok": false,
"error": {
"message": "El total del pedido no coincide con lo que suman sus líneas: mandaste 19000 y las líneas dan 22000.",
"code": 422,
"details": { "reason": "total_mismatch", "declared": 19000, "computed": 22000 }
}
}
Programá contra error.details.reason, no contra el texto: el texto es para la persona que lee el registro y puede cambiar.
| Status | Qué significa y qué hacer |
|---|---|
401 | Falta la key o no es válida (mal copiada o revocada). Revisá el encabezado; si sigue, pedile una key nueva al comercio. |
403 | La key no puede hacer esto. Reintentar no lo arregla: lo resuelve el comercio (ver la tabla de abajo). |
404 | No hay nada con ese identificador en este comercio. |
409 | El estado del comercio impide la operación. No reintentes igual: hace falta que alguien lo resuelva en Punto o esperar a que cambie. |
422 | El pedido tiene un dato que corregir. Corregilo y volvé a mandarlo. |
429 | Pasaste el límite de uso. Esperá y reintentá. |
5xx | Falla de Punto. Reintentá con el MISMO identificador: es seguro (ver Idempotencia). |
Los motivos más comunes y qué hacer:
reason | Qué hacer |
|---|---|
addon_api_required | El comercio no tiene activa la API de integraciones. Tiene que pedirle a Punto que la active. |
api_key_scope_missing | La key no se emitió como Integración de ventas. El comercio emite una nueva con esa opción. |
account_blocked, account_suspended, account_inactive | La cuenta del comercio en Punto no está operativa (por ejemplo, plan vencido). Lo resuelve el comercio con Punto. |
register_unavailable | La caja no existe, está inactiva o la key no tiene acceso a su sucursal. Revisá el registerId con GET /v1/integrations/registers. |
item_unavailable | Un artículo no existe o no está a la venta. details.line dice cuál. Volvé a sincronizar el catálogo. |
total_mismatch | El total no coincide con lo que suman las líneas. Revisá precios, cantidades y cargos (ver Precios, impuestos y totales). |
payment_method_unknown | El medio de pago no existe en el comercio. Usá un id de GET /v1/payment-methods. |
payment_method_cash | Se mandó efectivo. Una venta online no se cobra en el cajón del comercio: usá el medio con el que cobró tu sistema. |
customer_not_found | El cliente nombrado por id o externalId no existe en el comercio. Crealo primero o mandá sus datos. |
order_pending_acceptance | La orden todavía no fue aceptada. Esperá y consultá su estado. |
invoice_range_exhausted | La caja se quedó sin números de factura. El comercio tiene que cargar un timbrado nuevo. |
Cada operación lista todos sus motivos en su sección.
Idempotencia
Las altas llevan el identificador del registro en tu sistema: orderId en ventas y órdenes, externalId en clientes, channelRef en citas. Es lo que garantiza que nada se registre dos veces, aunque la conexión se corte.
Un ejemplo. Tu sistema manda la venta del pedido WEB-10245, la conexión se corta y no ves la respuesta. ¿Se registró o no? No hace falta averiguarlo: mandá exactamente la misma llamada otra vez.
- Si la primera había llegado, recibís la venta que ya existía, con el mismo número de factura,
duplicated: truey status200. - Si no había llegado, se registra ahora y recibís
201conduplicated: false.
En los dos casos queda una sola venta y una sola factura. Lo mismo vale si dos llamadas del mismo pedido llegan a la vez: se registra una y las dos reciben el mismo resultado. Un reintento devuelve lo registrado aunque el catálogo haya cambiado después: nunca te va a decir que falló un pedido que ya está facturado.
Reglas del identificador: letras, números, punto, guion y guion bajo; hasta 45 caracteres en orderId y 64 en externalId. Usá siempre el identificador estable de tu sistema, nunca uno generado en cada intento.
Ventas y órdenes tienen cada una su espacio de orderId: el mismo pedido puede entrar primero como orden y después como venta con el mismo identificador.
Precios, impuestos y totales
Cada línea nombra un artículo del catálogo de Punto (por itemId o por sku), la cantidad y el precio unitario que cobró tu sistema. Punto compara ese precio con el de su lista (la del cliente, la de la sucursal o la del catálogo, en ese orden):
- Por debajo de la lista, la diferencia queda registrada como descuento de la línea. Por ejemplo: el artículo vale 50.000 en Punto y tu tienda lo vendió a 45.000; la línea se registra a 50.000 con un descuento de 5.000. Así los reportes del comercio siguen mostrando el margen real.
- Por encima de la lista, se registra el precio que mandaste.
- Agregados (
addons): se nombran poroptionIdy cantidad; el recargo lo pone el catálogo. ElunitPricede la línea es el precio con los agregados incluidos. - Envíos y recargos van en
charges, como línea propia con su descripción. Nunca sumados al precio de un producto.
Los impuestos los calcula Punto con las mismas reglas que una venta en el local: tu sistema no manda impuestos ni totales por tasa. Tampoco manda el número de comprobante: lo asigna Punto. El impuesto de un cargo es el que indiques en taxId (ver GET /v1/taxes) o, si no indicás ninguno, el predeterminado del comercio.
El total se compara. En total mandás lo que cobraste (o vas a cobrar) al comprador, y Punto lo compara con lo que suman las líneas y los cargos. Si la diferencia es mayor al redondeo (una unidad de la moneda por línea), el pedido no se registra y responde 422 con reason: total_mismatch, declared (lo que mandaste) y computed (lo que suman las líneas). Nunca se factura algo distinto de lo que vio el comprador.
Los montos van en la moneda del comercio, como números (45000, no "45.000").
Clientes
Punto busca al comprador antes de crear uno nuevo, con la misma regla en todos los lugares donde se nombra a un comprador (el alta de clientes, las órdenes y las ventas). En este orden:
- Por su
iden Punto, si lo mandás. - Por su
externalId, el identificador del cliente en tu sistema. - Por su documento, aunque venga escrito distinto (
1.234.567y1234567son el mismo). - Por su teléfono, aunque venga en otro formato.
- Si no aparece, lo crea.
Cuando lo encuentra por documento o por teléfono, el cliente queda vinculado a tu externalId, así las próximas veces lo podés nombrar solo con él. matchedBy en la respuesta del alta te dice cómo lo encontró. Un cliente que el comercio ya tenía vinculado a otro externalId tuyo se devuelve igual, con su referencia original.
Documento. document es el documento con el que se le factura. documentType dice de qué clase es: fiscal (el identificador tributario; en Paraguay, el RUC) o personal (el documento de identidad; en Paraguay, la cédula). Si no lo mandás, Punto lo deduce del formato; mandarlo evita errores.
Teléfono. Mandalo con código de país, en formato internacional (+595981000111). Un número sin código de país se interpreta con el país del comercio.
Para no duplicar datos, dos clientes del comercio no pueden tener el mismo teléfono ni el mismo documento personal. Si una actualización choca con otro cliente, responde 409 y details.customerId dice con cuál.
En órdenes y ventas podés nombrar al comprador de tres maneras: con su externalId (si ya lo creaste), con su id de Punto, o con sus datos completos para que Punto lo busque o lo cree en el momento. Sin datos del comprador, la venta sale a consumidor final.
Factura electrónica y KuDE
La factura electrónica depende del país del comercio. Hoy Punto la emite en Paraguay (SIFEN). Si la caja de tu sistema tiene la facturación electrónica configurada, cada venta emite su documento; si no, la venta se registra igual, sin documento electrónico.
En Paraguay el documento se envía a validar en segundo plano, así que la respuesta del alta todavía no trae el resultado: se consulta después.
Cómo seguirlo. Consultá GET /v1/integrations/sales?orderId=<id> (o la orden, si la facturaste desde una orden) hasta que einvoice.sifenVerdict deje de ser pending. Una consulta cada 30 segundos alcanza; la mayoría se resuelve en pocos minutos.
einvoice | Qué significa |
|---|---|
status: null | La caja no emite factura electrónica. La venta está registrada igual. |
sifenVerdict: pending | Enviada, esperando validación. |
sifenVerdict: approved (issued: true) | Aprobada. cdc y documentNumber son los datos del documento. |
sifenVerdict: rejected | Rechazada; sifenReason dice por qué. La corrige el comercio desde su panel. No reenvíes el pedido: ya está registrado. |
issued: true significa que el documento fue aprobado, no solo enviado: un documento puede enviarse y ser rechazado después.
El KuDE. Es la representación imprimible de la factura electrónica, la que recibe el comprador. Queda disponible en el portal de Punto: einvoice.portalUrl es el link que tu sistema le muestra al comprador, por ejemplo en la confirmación del pedido o en el correo que le mandás. Esta API no devuelve el PDF. einvoice.qrUrl es el link de consulta pública del documento, por si querés imprimir su código QR en tu comprobante.
Órdenes: del pedido a la factura
Una orden es un pedido que el comercio prepara y entrega. Entra pendiente: en las cajas de la sucursal suena un aviso y alguien del comercio la acepta —y puede indicar en cuánto va a estar lista— o la rechaza con un motivo (out_of_stock, closed, too_busy, out_of_area u other). Recién aceptada pasa a la cola del equipo. Punto no te avisa de los cambios: consultá GET /v1/integrations/orders y mirá acceptance (pending; accepted, con etaMinutes y estimatedReadyAt; o rejected, con reason y note). Crear una orden no factura.
Cada orden trae trackingUrl: el link público donde tu comprador sigue su pedido desde el celular y se entera cuando está listo o en camino. Mostráselo en la confirmación de la compra.
Una orden aceptada se factura de dos maneras:
- En el local, como cualquier orden (por ejemplo, un envío que se cobra al entregarlo). Consultando
GET /v1/integrations/ordersvas a ver la venta ensalecuando se cobre. - Desde tu sistema, con
POST /v1/integrations/salesyfromOrderId: Punto registra la venta y la vincula a la orden. La orden se cierra cuando está facturada y entregada: un envío facturado antes de salir sigue abierto (y el comprador lo sigue entrackingUrl) hasta que se entrega. Si no mandás comprador, la venta toma el de la orden.
Una orden pendiente no se puede facturar (409 con order_pending_acceptance), y una rechazada tampoco (409 con order_rejected).
Para recibir órdenes, el comercio tiene que tener activado el módulo de órdenes; si no, la orden no entra y responde 422 con orders_module_disabled. Una orden con scheduledFor en otro día no le aparece al equipo hasta ese día. Una orden con envío (fulfillment: delivery) necesita al comprador y la dirección; para cobrar el envío con la tarifa del comercio, cotizalo con GET /v1/integrations/delivery-fee y mandalo como una línea más.
Agenda: servicios, horarios y citas
Si el comercio vende el tiempo de sus profesionales, tu sistema o tu bot pueden agendar con las mismas reglas que su caja. Necesita la agenda activada: sin ella, estas llamadas responden calendar_module_disabled.
- Qué se puede agendar.
GET /v1/integrations/servicesda los servicios, cuánto duran, cuánto salen y quién los hace. - Cuándo.
GET /v1/integrations/availabilityda los horarios libres de un día. SinuserIdvale cualquier profesional. - Agendar.
POST /v1/integrations/appointmentscon tuchannelRef. El cliente se nombra igual que en una orden: por teléfono o documento, Punto lo encuentra o lo crea, sin duplicarlo. - Mandarle el link. La respuesta trae
manageUrl: ahí su cliente ve la cita, la cambia de horario o la cancela, sin cuenta. YicsUrl, para sumarla a su calendario. Punto no se los manda: lo hacés vos.
Si el horario no entra, la respuesta es 422 con el motivo en error.details.reason y los horarios libres más cercanos en error.details.suggestions. Ofrecéselos a tu cliente en vez de pedirle que pruebe otra vez.
Anticipación. El comercio define con cuánto tiempo mínimo y hasta cuánto por adelantado se agenda por acá (por defecto, una hora y 60 días). Un horario fuera de esa ventana da booking_too_soon o booking_too_far.
Las fechas viajan en la hora del comercio, sin zona: 2026-11-10 10:30. Una cita a las 10:30 es a las 10:30 en el reloj del local.
Punto no te avisa de los cambios: el comercio puede mover o cancelar una cita desde su agenda, y su cliente desde el link. Consultá GET /v1/integrations/appointments cuando lo necesites.
Catálogo, stock y datos del comercio
Con la misma key leés los datos que tu sistema necesita para armar pedidos válidos:
| Qué | Cómo | Para qué |
|---|---|---|
| Cajas | GET /v1/integrations/registers | El registerId de cada pedido y si factura electrónicamente. |
| Catálogo | GET /v1/items | itemId, sku, precio de lista y agregados de cada artículo. |
| Stock | GET /v1/integrations/stock | Cuántas unidades hay de cada artículo en una sucursal. |
| Medios de pago | GET /v1/payment-methods | El id del medio con el que cobró tu sistema. |
| Impuestos | GET /v1/taxes | El taxId de un envío o recargo. |
El catálogo se recorre por páginas: pedí la primera con after vacío y seguí con el valor de next hasta que venga null. Para mantenerlo al día, volvé a recorrerlo periódicamente (por ejemplo, una vez por hora); los precios y la disponibilidad pueden cambiar en cualquier momento desde el panel del comercio.
Algunas respuestas traen más campos que los documentados. Usá solo los documentados: los demás pueden cambiar sin aviso.
Glosario
- Comercio: el negocio que usa Punto y te autorizó a conectar tu sistema.
- Sucursal: cada local del comercio. Tiene su stock, sus cajas y sus órdenes.
- Caja: el punto de venta contra el que se registra cada pedido. Decide si la venta sale con factura electrónica y con qué numeración.
- Punto de expedición: el código de establecimiento y punto de emisión que encabeza el número de factura (por ejemplo,
001-003). Lo tiene cargado cada caja que factura. - Timbrado: la autorización fiscal con la que la caja numera sus facturas. Cuando se agota o vence, la caja deja de facturar hasta que el comercio cargue uno nuevo.
- KuDE: la representación imprimible de una factura electrónica en Paraguay, la que recibe el comprador.
- CDC: el código de control que identifica a una factura electrónica en Paraguay.
- Orden: un pedido que el comercio prepara y entrega, y que se factura después.
- Venta: un pedido cobrado, que se registra y se factura en el acto.
- Consumidor final: una venta sin comprador identificado.
Clientes
Alta, actualización y consulta de los clientes del comercio.
Consultar o listar clientes
/v1/integrations/customersCon id o externalId, devuelve ese cliente. Sin ninguno de los dos, lista los clientes activos del comercio por páginas, ordenados por nombre, con búsqueda opcional por nombre, documento, teléfono o email.
Parámetros
| Campo | Tipo | Descripción |
|---|---|---|
id | string (uuid) | Identificador del cliente en Punto. Mandá este o externalId. |
externalId | string | Identificador del cliente en tu sistema. Mandá este o id. |
q | string | Búsqueda por nombre, documento, teléfono o email (sin distinguir tildes ni mayúsculas). |
limit | integer | Clientes por página (máximo 100). |
offset | integer | Cuántos saltear (para la página siguiente, sumá limit). |
Ejemplo de llamado
Un cliente por tu identificador
curl "$PUNTO_API/v1/integrations/customers?externalId=cliente-4812" \
-H "Authorization: Bearer $PUNTO_KEY"Buscar clientes
curl "$PUNTO_API/v1/integrations/customers?q=gomez&limit=50&offset=0" \
-H "Authorization: Bearer $PUNTO_KEY"Respuesta (dentro de data)
| Campo | Tipo | Descripción |
|---|---|---|
customer | object | El cliente pedido por id o externalId. |
customer.id | string (uuid) | Identificador del cliente en Punto. |
customer.externalId | string | null | Tu identificador del cliente, si lo vinculaste. |
customer.name | string | |
customer.fiscalName | string | null | |
customer.document | string | null | |
customer.documentType | string | null | Valores: fiscal, personal. |
customer.personalDocument | string | null | Cédula guardada en la ficha. |
customer.fiscalDocument | string | null | RUC guardado en la ficha. |
customer.email | string | null | |
customer.phone | string | null | En formato internacional, con +. |
customer.address | string | null | |
customer.createdAt | string | null | Fecha de alta en Punto. |
customers | array<object> | Una página del listado. |
customers[].id | string (uuid) | Identificador del cliente en Punto. |
customers[].externalId | string | null | Tu identificador del cliente, si lo vinculaste. |
customers[].name | string | |
customers[].fiscalName | string | null | |
customers[].document | string | null | |
customers[].documentType | string | null | Valores: fiscal, personal. |
customers[].personalDocument | string | null | Cédula guardada en la ficha. |
customers[].fiscalDocument | string | null | RUC guardado en la ficha. |
customers[].email | string | null | |
customers[].phone | string | null | En formato internacional, con +. |
customers[].address | string | null | |
customers[].createdAt | string | null | Fecha de alta en Punto. |
total | integer | Cantidad de clientes que coinciden con la búsqueda. |
limit | integer | |
offset | integer |
Respuesta de ejemplo
200 · El cliente (con id o externalId), o una página del listado.
{
"ok": true,
"data": {
"customers": [
{
"id": "5d0c8a2e-3f41-4b7a-9e62-1c8d7f3a9b10",
"externalId": "cliente-4812",
"name": "Ana Gómez",
"fiscalName": null,
"document": "4567890",
"documentType": "personal",
"email": "[email protected]",
"phone": "+595981000111",
"address": "Av. España 1234",
"createdAt": "2026-10-03 10:14:22"
}
],
"total": 1,
"limit": 50,
"offset": 0
}
}Códigos de respuesta
| 200 | El cliente (con id o externalId), o una página del listado. |
| 401 | Falta la key o no es válida. |
| 403 | La key no puede hacer esto. reason: addon_api_required (el comercio no tiene activa la API de integraciones), api_key_scope_missing (la key no es de integración), account_blocked, account_suspended o account_inactive (la cuenta del comercio no está operativa). Si el usuario que emitió la key no tiene permiso para vender, o si pedís una sucursal (outletId) a la que la key no tiene acceso, responde 403 sin reason. |
| 404 | No hay nada con ese identificador en este comercio. |
| 429 | Pasaste el límite de uso de la key (60 por minuto, 5.000 por día). Esperá un minuto. |
Crear un cliente
/v1/integrations/customersDa de alta un cliente del comercio con tu identificador (externalId). Antes de crearlo, Punto lo busca por externalId, documento y teléfono (ver Clientes): si ya existía, te lo devuelve sin cambiar sus datos y lo vincula a tu externalId. Para cambiar sus datos, usá PATCH.
Responde 201 si lo creó y 200 si ya existía.
Motivos de rechazo (error.details.reason, todos 422): customer_name_required (no existía y falta el nombre), customer_invalid (un dato con formato inválido, por ejemplo un teléfono que no es de ningún país), customer_not_found.
Cuerpo de la petición
| Campo | Tipo | Descripción |
|---|---|---|
externalIdobligatorio | string | Identificador del cliente en tu sistema. Ver Idempotencia. Hasta 64 caracteres. |
name | string | Nombre y apellido. Hasta 120 caracteres. |
fiscalName | string | Razón social para la factura, si es distinta del nombre. Hasta 160 caracteres. |
document | string | Documento con el que se le factura. Al actualizarlo, reemplaza al anterior. Para guardar cédula y RUC a la vez usá personalDocument y fiscalDocument. Hasta 30 caracteres. |
documentType | string | fiscal (en Paraguay, RUC) o personal (en Paraguay, cédula). Sin él, Punto lo deduce del formato. Valores: fiscal, personal. |
personalDocument | string | Cédula (documento personal) de la persona. Va junto al RUC en la misma ficha: mandá los dos cuando los tengas. |
fiscalDocument | string | RUC (documento fiscal) con el que se factura, junto a fiscalName. Va junto a la cédula en la misma ficha. |
email | string (email) | Hasta 120 caracteres. |
phone | string | Con código de país, en formato internacional (+595981000111). Sin código, se toma el país del comercio. Hasta 30 caracteres. |
address | string | Dirección principal. Hasta 250 caracteres. |
Ejemplo de llamado
Alta de un cliente
curl -X POST "$PUNTO_API/v1/integrations/customers" \
-H "Authorization: Bearer $PUNTO_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalId": "cliente-4812",
"name": "Ana Gómez",
"document": "4567890",
"documentType": "personal",
"email": "[email protected]",
"phone": "+595981000111",
"address": "Av. España 1234"
}'Respuesta (dentro de data)
| Campo | Tipo | Descripción |
|---|---|---|
customer | object | Un cliente del comercio. |
customer.id | string (uuid) | Identificador del cliente en Punto. |
customer.externalId | string | null | Tu identificador del cliente, si lo vinculaste. |
customer.name | string | |
customer.fiscalName | string | null | |
customer.document | string | null | |
customer.documentType | string | null | Valores: fiscal, personal. |
customer.personalDocument | string | null | Cédula guardada en la ficha. |
customer.fiscalDocument | string | null | RUC guardado en la ficha. |
customer.email | string | null | |
customer.phone | string | null | En formato internacional, con +. |
customer.address | string | null | |
customer.createdAt | string | null | Fecha de alta en Punto. |
duplicated | boolean | true si ya existía un cliente con ese externalId. |
matchedBy | string | null | Si el cliente ya existía en el comercio, cómo se lo encontró. null si se creó o si coincidió la externalId. Valores: document, phone. |
Respuesta de ejemplo
200 · El cliente ya existía (por la misma externalId, o por documento o teléfono).
{
"ok": true,
"data": {
"customer": {
"id": "5d0c8a2e-3f41-4b7a-9e62-1c8d7f3a9b10",
"externalId": "cliente-4812",
"name": "Ana Gómez",
"fiscalName": null,
"document": "4567890",
"documentType": "personal",
"email": "[email protected]",
"phone": "+595981000111",
"address": "Av. España 1234",
"createdAt": "2026-10-03 10:14:22"
},
"duplicated": false,
"matchedBy": "document"
}
}201 · Cliente creado.
{
"ok": true,
"data": {
"customer": {
"id": "5d0c8a2e-3f41-4b7a-9e62-1c8d7f3a9b10",
"externalId": "cliente-4812",
"name": "Ana Gómez",
"fiscalName": null,
"document": "4567890",
"documentType": "personal",
"email": "[email protected]",
"phone": "+595981000111",
"address": "Av. España 1234",
"createdAt": "2026-10-03 10:14:22"
},
"duplicated": false,
"matchedBy": null
}
}Códigos de respuesta
| 200 | El cliente ya existía (por la misma externalId, o por documento o teléfono). |
| 201 | Cliente creado. |
| 401 | Falta la key o no es válida. |
| 403 | La key no puede hacer esto. reason: addon_api_required (el comercio no tiene activa la API de integraciones), api_key_scope_missing (la key no es de integración), account_blocked, account_suspended o account_inactive (la cuenta del comercio no está operativa). Si el usuario que emitió la key no tiene permiso para vender, o si pedís una sucursal (outletId) a la que la key no tiene acceso, responde 403 sin reason. |
| 422 | Hay un dato que corregir. Ver reason. |
| 429 | Pasaste el límite de uso de la key (60 por minuto, 5.000 por día). Esperá un minuto. |
Actualizar un cliente
/v1/integrations/customersCambia los datos de un cliente, nombrado por id o por externalId. Solo cambia lo que mandás.
Motivos de rechazo (error.details.reason): customer_not_found, customer_name_required (el cliente quedaría sin nombre), customer_invalid (422); customer_phone_taken, customer_document_taken (409: otro cliente ya tiene ese teléfono o documento; details.customerId dice cuál).
Parámetros
| Campo | Tipo | Descripción |
|---|---|---|
id | string (uuid) | Identificador del cliente en Punto. Mandá este o externalId. |
externalId | string | Identificador del cliente en tu sistema. Mandá este o id. |
Cuerpo de la petición
| Campo | Tipo | Descripción |
|---|---|---|
name | string | Nombre y apellido. Hasta 120 caracteres. |
fiscalName | string | Razón social para la factura, si es distinta del nombre. Hasta 160 caracteres. |
document | string | Documento con el que se le factura. Al actualizarlo, reemplaza al anterior. Para guardar cédula y RUC a la vez usá personalDocument y fiscalDocument. Hasta 30 caracteres. |
documentType | string | fiscal (en Paraguay, RUC) o personal (en Paraguay, cédula). Sin él, Punto lo deduce del formato. Valores: fiscal, personal. |
personalDocument | string | Cédula (documento personal) de la persona. Va junto al RUC en la misma ficha: mandá los dos cuando los tengas. |
fiscalDocument | string | RUC (documento fiscal) con el que se factura, junto a fiscalName. Va junto a la cédula en la misma ficha. |
email | string (email) | Hasta 120 caracteres. |
phone | string | Con código de país, en formato internacional (+595981000111). Sin código, se toma el país del comercio. Hasta 30 caracteres. |
address | string | Dirección principal. Hasta 250 caracteres. |
Ejemplo de llamado
Cambiar email y teléfono
curl -X PATCH "$PUNTO_API/v1/integrations/customers?externalId=cliente-4812" \
-H "Authorization: Bearer $PUNTO_KEY" \
-H "Content-Type: application/json" \
-d '{ "email": "[email protected]", "phone": "+595981000222" }'Respuesta (dentro de data)
| Campo | Tipo | Descripción |
|---|---|---|
customer | object | Un cliente del comercio. |
customer.id | string (uuid) | Identificador del cliente en Punto. |
customer.externalId | string | null | Tu identificador del cliente, si lo vinculaste. |
customer.name | string | |
customer.fiscalName | string | null | |
customer.document | string | null | |
customer.documentType | string | null | Valores: fiscal, personal. |
customer.personalDocument | string | null | Cédula guardada en la ficha. |
customer.fiscalDocument | string | null | RUC guardado en la ficha. |
customer.email | string | null | |
customer.phone | string | null | En formato internacional, con +. |
customer.address | string | null | |
customer.createdAt | string | null | Fecha de alta en Punto. |
Respuesta de ejemplo
200 · El cliente actualizado.
{
"ok": true,
"data": {
"customer": {
"id": "5d0c8a2e-3f41-4b7a-9e62-1c8d7f3a9b10",
"externalId": "cliente-4812",
"name": "Ana Gómez",
"fiscalName": null,
"document": "4567890",
"documentType": "personal",
"email": "[email protected]",
"phone": "+595981000222",
"address": "Av. España 1234",
"createdAt": "2026-10-03 10:14:22"
}
}
}Códigos de respuesta
| 200 | El cliente actualizado. |
| 401 | Falta la key o no es válida. |
| 403 | La key no puede hacer esto. reason: addon_api_required (el comercio no tiene activa la API de integraciones), api_key_scope_missing (la key no es de integración), account_blocked, account_suspended o account_inactive (la cuenta del comercio no está operativa). Si el usuario que emitió la key no tiene permiso para vender, o si pedís una sucursal (outletId) a la que la key no tiene acceso, responde 403 sin reason. |
| 404 | No hay nada con ese identificador en este comercio. |
| 409 | El estado del comercio impide la operación. No reintentes igual: ver reason. |
| 422 | Hay un dato que corregir. Ver reason. |
| 429 | Pasaste el límite de uso de la key (60 por minuto, 5.000 por día). Esperá un minuto. |
Órdenes
Pedidos que el comercio prepara y entrega, y que se facturan después.
Consultar una orden
/v1/integrations/ordersEn qué etapa está la orden (status), qué decidió el comercio (acceptance) y, si ya se facturó —desde tu sistema o en el local—, la venta con su estado fiscal en sale.
Parámetros
| Campo | Tipo | Descripción |
|---|---|---|
orderIdobligatorio | string | El identificador del pedido en tu sistema, el mismo que mandaste al registrarlo. |
Ejemplo de llamado
Estado de la orden
curl "$PUNTO_API/v1/integrations/orders?orderId=WEB-10246" \
-H "Authorization: Bearer $PUNTO_KEY"Respuesta (dentro de data)
| Campo | Tipo | Descripción |
|---|---|---|
orderId | string | Tu identificador del pedido. |
id | string (uuid) | Identificador de la orden en Punto. |
number | integer | null | Número de la orden en la sucursal (el que ve el equipo). |
status | string | pending: pendiente (el comercio todavía no decidió); sent: en espera (aceptada, nadie la empezó); in_progress: en proceso; ready: lista; out_for_delivery: en camino; delivered: entregada; closed: facturada y entregada; cancelled: cancelada o rechazada. Valores: pending, open, sent, in_progress, ready, out_for_delivery, delivered, closed, cancelled. |
fulfillment | string | Valores: delivery, takeaway, dine_in. |
scheduledFor | string | null | |
createdAt | string | |
registerId | string (uuid) | null | |
customerId | string (uuid) | null | Identificador del comprador en Punto. |
delivery | object | null | La dirección con la que se registró la orden. |
delivery.address | string | null | |
delivery.reference | string | null | |
delivery.lat | number | null | |
delivery.lng | number | null | |
total | number | Total de la orden según sus líneas. |
acceptance | object | Qué decidió el comercio sobre el pedido. status sigue el ciclo completo de la orden; esto contesta si se aceptó. |
acceptance.status | string | pending: todavía no decidió; accepted: la aceptó; rejected: la rechazó (la orden queda cancelled). Valores: pending, accepted, rejected. |
acceptance.decidedAt | string | null | Cuándo la aceptó o rechazó. |
acceptance.etaMinutes | integer | null | Minutos que el comercio estimó al aceptar (opcional). |
acceptance.estimatedReadyAt | string | null | Cuándo estaría lista. Viene en toda orden aceptada; null mientras está pendiente o si se rechazó. |
acceptance.reason | string | null | Motivo del rechazo. Valores: out_of_stock, closed, too_busy, out_of_area, other. |
acceptance.reasonLabel | string | null | El motivo en palabras, para mostrarle al comprador. |
acceptance.note | string | null | Detalle que escribió el comercio al rechazar. |
trackingUrl | string (uri) | null | Link público de seguimiento para tu comprador. Muestra la etapa del pedido, nunca los productos, los montos, la dirección ni el nombre. Deja de responder 12 horas después del último movimiento. |
duplicated | boolean | Solo en el alta: true si la orden ya estaba registrada. |
invoiced | boolean | Si ya tiene una venta. |
sale | object | null | La venta que la facturó, con su estado fiscal. |
sale.transactionId | string (uuid) | |
sale.uid | string | |
sale.document | object | null | El comprobante de la venta. |
sale.document.number | string | null | Número de comprobante con su prefijo, listo para mostrar. |
sale.document.invoiceNo | integer | null | El número, sin prefijo. |
sale.document.prefix | string | null | Punto de expedición (ver Glosario). |
sale.document.serie | string | null | |
sale.document.timbrado | string | null | Timbrado con el que se numeró. |
sale.document.date | string | null | Fecha y hora de la venta. |
sale.document.total | number | null | |
sale.einvoice | object | Estado de la factura electrónica. Ver Factura electrónica y KuDE. |
sale.einvoice.issued | boolean | true solo si fue aprobada. |
sale.einvoice.status | string | null | Estado del envío. null: la caja no emite factura electrónica. Valores: pending, sending, issued, error, cancelled, skipped. |
sale.einvoice.sifenVerdict | string | null | Resultado de la validación fiscal (en Paraguay, SIFEN). Valores: pending, approved, rejected. |
sale.einvoice.sifenReason | string | null | Motivo del rechazo. |
sale.einvoice.cdc | string | null | Código de control del documento. |
sale.einvoice.documentNumber | string | null | |
sale.einvoice.portalUrl | string (uri) | null | Link donde el comprador descarga su KuDE. |
sale.einvoice.qrUrl | string (uri) | null | Link de consulta pública del documento, para imprimir su QR. null mientras el documento no se aprobó. |
Respuesta de ejemplo
200 · La orden. En el ejemplo, aceptada y ya facturada.
{
"ok": true,
"data": {
"orderId": "WEB-10246",
"id": "9b2e4c71-0d6a-4f38-b5e2-3a1c8d7e6f90",
"number": 87,
"status": "closed",
"fulfillment": "delivery",
"scheduledFor": null,
"createdAt": "2026-10-03 10:15:02",
"registerId": "81c541da-640e-4891-a1a0-b32841e64c75",
"customerId": "5d0c8a2e-3f41-4b7a-9e62-1c8d7f3a9b10",
"delivery": {
"address": "Av. España 1234",
"reference": "Portón negro",
"lat": -25.2867,
"lng": -57.647
},
"total": 52000,
"acceptance": {
"status": "accepted",
"decidedAt": "2026-10-03 10:16:40",
"etaMinutes": 25,
"estimatedReadyAt": "2026-10-03 10:41:40",
"reason": null,
"reasonLabel": null,
"note": null
},
"trackingUrl": "https://app.punto.la/pedido/q3Zt8wLx0VbN4kRp2sYc7A",
"invoiced": true,
"sale": {
"transactionId": "c3f1a7d2-8e45-4b90-a6c1-7d2e9f0b4a58",
"uid": "ecom:WEB-10246",
"document": {
"number": "001-003-0000154",
"invoiceNo": 154,
"prefix": "001-003",
"serie": null,
"timbrado": "16234567",
"date": "2026-10-03 10:20:05",
"total": 52000
},
"einvoice": {
"issued": true,
"status": "issued",
"sifenVerdict": "approved",
"sifenReason": null,
"cdc": "01800123456001003000015412026100312345678901",
"documentNumber": "001-003-0000154",
"portalUrl": "https://app.punto.la/factura/eyJ0IjoiYzNmMWE3ZDIifQ.Xb2k",
"qrUrl": "https://ekuatia.set.gov.py/consultas/qr?nVersion=150&Id=01800123456001003000015412026100312345678901"
}
}
}
}Códigos de respuesta
| 200 | La orden. En el ejemplo, aceptada y ya facturada. |
| 401 | Falta la key o no es válida. |
| 403 | La key no puede hacer esto. reason: addon_api_required (el comercio no tiene activa la API de integraciones), api_key_scope_missing (la key no es de integración), account_blocked, account_suspended o account_inactive (la cuenta del comercio no está operativa). Si el usuario que emitió la key no tiene permiso para vender, o si pedís una sucursal (outletId) a la que la key no tiene acceso, responde 403 sin reason. |
| 404 | No hay nada con ese identificador en este comercio. |
| 422 | Hay un dato que corregir. Ver reason. |
| 429 | Pasaste el límite de uso de la key (60 por minuto, 5.000 por día). Esperá un minuto. |
Registrar una orden
/v1/integrations/ordersRegistra un pedido que el comercio prepara y entrega. Entra pendiente (status: pending): el equipo no lo empieza hasta que alguien del comercio lo acepta, y puede rechazarlo con un motivo. Consultá acceptance con GET /v1/integrations/orders. No se factura: se factura después en el local o con POST /v1/integrations/sales y fromOrderId (ver Órdenes: del pedido a la factura).
Motivos de rechazo (error.details.reason, todos 422): register_unavailable, item_unavailable, addons_invalid, price_below_addons, total_mismatch, tax_unavailable, customer_not_found, customer_name_required, customer_invalid, delivery_requires_customer, delivery_address_required, order_invalid, orders_module_disabled (el comercio no tiene activado el módulo de órdenes: la orden no entra hasta que lo active).
Cuerpo de la petición
| Campo | Tipo | Descripción |
|---|---|---|
orderIdobligatorio | string | Identificador del pedido en tu sistema. Ver Idempotencia. Hasta 45 caracteres. |
registerIdobligatorio | string (uuid) | La caja; la orden queda en su sucursal. |
fulfillmentobligatorio | string | Cómo llega al comprador: se envía, lo retira o se consume en el local. Valores: delivery, takeaway, dine_in. |
itemsobligatorio | array<object> | Hasta 200 elementos. |
items[].itemId | string (uuid) | Identificador del artículo en Punto. |
items[].sku | string | Código del artículo, si no mandás itemId. Hasta 120 caracteres. |
items[].quantityobligatorio | number | |
items[].unitPriceobligatorio | number | Precio por unidad que cobró tu sistema, con los agregados incluidos. |
items[].note | string | Nota de la línea (en una orden, la ve el equipo que la prepara). Hasta 200 caracteres. |
items[].addons | array<object> | Agregados del artículo (opciones de sus grupos). El recargo lo pone el catálogo. Hasta 50 elementos. |
items[].addons[].optionIdobligatorio | string (uuid) | La opción, de los addonGroups del artículo en el catálogo. |
items[].addons[].quantity | integer | Por defecto: 1. |
charges | array<object> | Hasta 200 elementos. |
charges[].descriptionobligatorio | string | Hasta 120 caracteres. |
charges[].amountobligatorio | number | |
charges[].taxId | string (uuid) | Impuesto del cargo (GET /v1/taxes). Sin él, el predeterminado del comercio. |
totalobligatorio | number | Lo que el comprador aprobó. Se compara con lo que suman las líneas. |
customer | object | Obligatorio con fulfillment: delivery. |
customer.externalId | string | Identificador del cliente en tu sistema. Hasta 64 caracteres. |
customer.id | string (uuid) | Identificador del cliente en Punto. |
customer.document | string | Documento con el que se le factura. Para guardar cédula y RUC a la vez usá personalDocument y fiscalDocument. Hasta 30 caracteres. |
customer.documentType | string | fiscal (en Paraguay, RUC) o personal (en Paraguay, cédula). Sin él, Punto lo deduce del formato. Valores: fiscal, personal. |
customer.personalDocument | string | Cédula (documento personal) de la persona. Va junto al RUC en la misma ficha: mandá los dos cuando los tengas. |
customer.fiscalDocument | string | RUC (documento fiscal) con el que se factura, junto a fiscalName. Va junto a la cédula en la misma ficha. |
customer.name | string | Nombre y apellido. Hace falta (o fiscalName) para crear un cliente nuevo. Hasta 120 caracteres. |
customer.fiscalName | string | Razón social para la factura, si es distinta del nombre. Hasta 160 caracteres. |
customer.email | string (email) | |
customer.phone | string | Con código de país, en formato internacional (por ejemplo +595981000111). |
delivery | object | Obligatorio con fulfillment: delivery. |
delivery.addressobligatorio | string | Hasta 250 caracteres. |
delivery.reference | string | Cómo encontrar el lugar. Hasta 250 caracteres. |
delivery.lat | number | Van juntas con lng, o ninguna. Entre -90 y 90. |
delivery.lng | number | Entre -180 y 180. |
delivery.name | string | Nombre de la dirección en la ficha del comprador (por ejemplo, Casa). Hasta 80 caracteres. |
delivery.city | string | Hasta 80 caracteres. |
scheduledFor | string | Para cuándo es: fecha (AAAA-MM-DD) o fecha y hora ISO 8601. Sin él, es para ahora. |
note | string | Hasta 500 caracteres. |
Ejemplo de llamado
Orden con envío
curl -X POST "$PUNTO_API/v1/integrations/orders" \
-H "Authorization: Bearer $PUNTO_KEY" \
-H "Content-Type: application/json" \
-d '{
"orderId": "WEB-10246",
"registerId": "81c541da-640e-4891-a1a0-b32841e64c75",
"fulfillment": "delivery",
"items": [
{
"sku": "HAMB-CLASICA",
"quantity": 1,
"unitPrice": 42000,
"note": "Sin cebolla",
"addons": [ { "optionId": "c1a2b3c4-d5e6-4f70-8a91-b2c3d4e5f604", "quantity": 1 } ]
}
],
"charges": [ { "description": "Envío a domicilio", "amount": 10000 } ],
"total": 52000,
"customer": { "externalId": "cliente-4812" },
"delivery": {
"address": "Av. España 1234",
"reference": "Portón negro",
"lat": -25.2867,
"lng": -57.647
}
}'Respuesta (dentro de data)
| Campo | Tipo | Descripción |
|---|---|---|
orderId | string | Tu identificador del pedido. |
id | string (uuid) | Identificador de la orden en Punto. |
number | integer | null | Número de la orden en la sucursal (el que ve el equipo). |
status | string | pending: pendiente (el comercio todavía no decidió); sent: en espera (aceptada, nadie la empezó); in_progress: en proceso; ready: lista; out_for_delivery: en camino; delivered: entregada; closed: facturada y entregada; cancelled: cancelada o rechazada. Valores: pending, open, sent, in_progress, ready, out_for_delivery, delivered, closed, cancelled. |
fulfillment | string | Valores: delivery, takeaway, dine_in. |
scheduledFor | string | null | |
createdAt | string | |
registerId | string (uuid) | null | |
customerId | string (uuid) | null | Identificador del comprador en Punto. |
delivery | object | null | La dirección con la que se registró la orden. |
delivery.address | string | null | |
delivery.reference | string | null | |
delivery.lat | number | null | |
delivery.lng | number | null | |
total | number | Total de la orden según sus líneas. |
acceptance | object | Qué decidió el comercio sobre el pedido. status sigue el ciclo completo de la orden; esto contesta si se aceptó. |
acceptance.status | string | pending: todavía no decidió; accepted: la aceptó; rejected: la rechazó (la orden queda cancelled). Valores: pending, accepted, rejected. |
acceptance.decidedAt | string | null | Cuándo la aceptó o rechazó. |
acceptance.etaMinutes | integer | null | Minutos que el comercio estimó al aceptar (opcional). |
acceptance.estimatedReadyAt | string | null | Cuándo estaría lista. Viene en toda orden aceptada; null mientras está pendiente o si se rechazó. |
acceptance.reason | string | null | Motivo del rechazo. Valores: out_of_stock, closed, too_busy, out_of_area, other. |
acceptance.reasonLabel | string | null | El motivo en palabras, para mostrarle al comprador. |
acceptance.note | string | null | Detalle que escribió el comercio al rechazar. |
trackingUrl | string (uri) | null | Link público de seguimiento para tu comprador. Muestra la etapa del pedido, nunca los productos, los montos, la dirección ni el nombre. Deja de responder 12 horas después del último movimiento. |
duplicated | boolean | Solo en el alta: true si la orden ya estaba registrada. |
invoiced | boolean | Si ya tiene una venta. |
sale | object | null | La venta que la facturó, con su estado fiscal. |
sale.transactionId | string (uuid) | |
sale.uid | string | |
sale.document | object | null | El comprobante de la venta. |
sale.document.number | string | null | Número de comprobante con su prefijo, listo para mostrar. |
sale.document.invoiceNo | integer | null | El número, sin prefijo. |
sale.document.prefix | string | null | Punto de expedición (ver Glosario). |
sale.document.serie | string | null | |
sale.document.timbrado | string | null | Timbrado con el que se numeró. |
sale.document.date | string | null | Fecha y hora de la venta. |
sale.document.total | number | null | |
sale.einvoice | object | Estado de la factura electrónica. Ver Factura electrónica y KuDE. |
sale.einvoice.issued | boolean | true solo si fue aprobada. |
sale.einvoice.status | string | null | Estado del envío. null: la caja no emite factura electrónica. Valores: pending, sending, issued, error, cancelled, skipped. |
sale.einvoice.sifenVerdict | string | null | Resultado de la validación fiscal (en Paraguay, SIFEN). Valores: pending, approved, rejected. |
sale.einvoice.sifenReason | string | null | Motivo del rechazo. |
sale.einvoice.cdc | string | null | Código de control del documento. |
sale.einvoice.documentNumber | string | null | |
sale.einvoice.portalUrl | string (uri) | null | Link donde el comprador descarga su KuDE. |
sale.einvoice.qrUrl | string (uri) | null | Link de consulta pública del documento, para imprimir su QR. null mientras el documento no se aprobó. |
Respuesta de ejemplo
200 · La orden ya estaba registrada (duplicated: true).
{
"ok": true,
"data": {
"orderId": "WEB-10246",
"id": "9b2e4c71-0d6a-4f38-b5e2-3a1c8d7e6f90",
"number": 87,
"status": "pending",
"fulfillment": "delivery",
"scheduledFor": null,
"createdAt": "2026-10-03 10:15:02",
"registerId": "81c541da-640e-4891-a1a0-b32841e64c75",
"customerId": "5d0c8a2e-3f41-4b7a-9e62-1c8d7f3a9b10",
"delivery": {
"address": "Av. España 1234",
"reference": "Portón negro",
"lat": -25.2867,
"lng": -57.647
},
"total": 52000,
"acceptance": {
"status": "pending",
"decidedAt": null,
"etaMinutes": null,
"estimatedReadyAt": null,
"reason": null,
"reasonLabel": null,
"note": null
},
"trackingUrl": "https://app.punto.la/pedido/q3Zt8wLx0VbN4kRp2sYc7A",
"duplicated": true,
"invoiced": false,
"sale": null
}
}201 · Orden registrada, pendiente de aceptación.
{
"ok": true,
"data": {
"orderId": "WEB-10246",
"id": "9b2e4c71-0d6a-4f38-b5e2-3a1c8d7e6f90",
"number": 87,
"status": "pending",
"fulfillment": "delivery",
"scheduledFor": null,
"createdAt": "2026-10-03 10:15:02",
"registerId": "81c541da-640e-4891-a1a0-b32841e64c75",
"customerId": "5d0c8a2e-3f41-4b7a-9e62-1c8d7f3a9b10",
"delivery": {
"address": "Av. España 1234",
"reference": "Portón negro",
"lat": -25.2867,
"lng": -57.647
},
"total": 52000,
"acceptance": {
"status": "pending",
"decidedAt": null,
"etaMinutes": null,
"estimatedReadyAt": null,
"reason": null,
"reasonLabel": null,
"note": null
},
"trackingUrl": "https://app.punto.la/pedido/q3Zt8wLx0VbN4kRp2sYc7A",
"duplicated": false,
"invoiced": false,
"sale": null
}
}Códigos de respuesta
| 200 | La orden ya estaba registrada (duplicated: true). |
| 201 | Orden registrada, pendiente de aceptación. |
| 401 | Falta la key o no es válida. |
| 403 | La key no puede hacer esto. reason: addon_api_required (el comercio no tiene activa la API de integraciones), api_key_scope_missing (la key no es de integración), account_blocked, account_suspended o account_inactive (la cuenta del comercio no está operativa). Si el usuario que emitió la key no tiene permiso para vender, o si pedís una sucursal (outletId) a la que la key no tiene acceso, responde 403 sin reason. |
| 422 | Hay un dato que corregir. Ver reason. |
| 429 | Pasaste el límite de uso de la key (60 por minuto, 5.000 por día). Esperá un minuto. |
Cotizar un envío
/v1/integrations/delivery-feeCuánto cuesta enviar un pedido desde una sucursal hasta una ubicación, con las mismas tarifas por distancia que usa el comercio en el local.
La distancia es la de manejo por calles; si no se puede calcular, se usa la línea recta con un factor de corrección (method: road o straight). Con status: ok, itemId es el artículo de envío que corresponde y price su precio: mandalo como una línea más del pedido, igual que cualquier artículo. Con no_coverage, la ubicación está fuera del área de entrega del comercio. no_rules: el comercio no configuró tarifas de envío. no_origin: la sucursal no tiene su ubicación cargada. not_sold_here: el artículo de envío que corresponde a esa distancia no está asignado a esa sucursal (o ya no está a la venta), así que no se cotiza.
Parámetros
| Campo | Tipo | Descripción |
|---|---|---|
outletIdobligatorio | string (uuid) | Sucursal desde la que sale el envío (outletId de GET /v1/integrations/registers). |
latobligatorio | number | Latitud del destino. |
lngobligatorio | number | Longitud del destino. |
Ejemplo de llamado
Cotizar el envío
curl "$PUNTO_API/v1/integrations/delivery-fee?outletId=1a282724-6073-49c3-8bc3-0114a132e349&lat=-25.2867&lng=-57.6470" \
-H "Authorization: Bearer $PUNTO_KEY"Respuesta (dentro de data)
| Campo | Tipo | Descripción |
|---|---|---|
statusobligatorio | string | Resultado de la cotización. Valores: ok, no_coverage, no_rules, no_origin, not_sold_here. |
method | string | null | Cómo se midió la distancia. Valores: road, straight. |
distanceMeters | integer | null | Distancia en metros. |
maxMeters | integer | null | Hasta dónde llega la tarifa que corresponde. |
itemId | string (uuid) | null | Artículo de envío a cobrar. |
itemName | string | null | Nombre del artículo de envío. |
price | number | null | Precio del artículo de envío. |
quotedAt | string (date-time) | null | Momento de la cotización. |
Respuesta de ejemplo
200 · La cotización.
{
"ok": true,
"data": {
"status": "ok",
"method": "road",
"distanceMeters": 3840,
"maxMeters": 5000,
"itemId": "4f6a1c2e-9b37-4d80-a5e1-2c7d9f3b8a61",
"itemName": "Envío hasta 5 km",
"price": 10000,
"quotedAt": "2026-10-03T10:12:44-03:00"
}
}Códigos de respuesta
| 200 | La cotización. |
| 401 | Falta la key o no es válida. |
| 403 | La key no puede hacer esto. reason: addon_api_required (el comercio no tiene activa la API de integraciones), api_key_scope_missing (la key no es de integración), account_blocked, account_suspended o account_inactive (la cuenta del comercio no está operativa). Si el usuario que emitió la key no tiene permiso para vender, o si pedís una sucursal (outletId) a la que la key no tiene acceso, responde 403 sin reason. |
| 422 | Hay un dato que corregir. Ver reason. |
| 429 | Pasaste el límite de uso de la key (60 por minuto, 5.000 por día). Esperá un minuto. |
Agenda
Servicios, horarios libres y citas con los profesionales del comercio.
Consultar los servicios
/v1/integrations/servicesLos servicios que se pueden agendar: los que hace algún profesional de la agenda, en las sucursales a las que la key tiene acceso. Cada uno con su duración, su precio y quién lo hace. Con outletId, solo lo de esa sucursal y el precio de su lista.
El precio es de referencia: la cita se cobra al precio vigente el día que se atiende.
Motivos de rechazo (error.details.reason, 422): calendar_module_disabled (el comercio no tiene activada la agenda), outlet_unavailable.
Parámetros
| Campo | Tipo | Descripción |
|---|---|---|
outletId | string (uuid) | La sucursal (outletId de GET /v1/integrations/registers). |
Ejemplo de llamado
Servicios de una sucursal
curl "$PUNTO_API/v1/integrations/services?outletId=1a282724-6073-49c3-8bc3-0114a132e349" \
-H "Authorization: Bearer $PUNTO_KEY"Respuesta (dentro de data)
| Campo | Tipo | Descripción |
|---|---|---|
services | array<object> | |
services[].itemId | string (uuid) | |
services[].name | string | |
services[].sku | string | null | |
services[].durationMinutes | integer | Cuánto dura. Sin duración cargada, 30 minutos. |
services[].price | number | Precio de lista, de referencia. La cita se cobra al precio vigente el día que se atiende. |
services[].professionals | array<object> | |
services[].professionals[].userId | string (uuid) | |
services[].professionals[].name | string | |
services[].professionals[].outletIds | array<string (uuid)> | Dónde lo hace. |
Respuesta de ejemplo
200 · Los servicios.
{
"ok": true,
"data": {
"services": [
{
"itemId": "61230b1e-90e8-4018-ac59-865ca957b293",
"name": "Servicio de ejemplo",
"sku": "SERV-01",
"durationMinutes": 45,
"price": 80000,
"professionals": [
{
"userId": "3e52da17-74a2-49c3-9d07-8d4806671fd5",
"name": "Ana Gómez",
"outletIds": [
"1a282724-6073-49c3-8bc3-0114a132e349"
]
}
]
}
]
}
}Códigos de respuesta
| 200 | Los servicios. |
| 401 | Falta la key o no es válida. |
| 403 | La key no puede hacer esto. reason: addon_api_required (el comercio no tiene activa la API de integraciones), api_key_scope_missing (la key no es de integración), account_blocked, account_suspended o account_inactive (la cuenta del comercio no está operativa). Si el usuario que emitió la key no tiene permiso para vender, o si pedís una sucursal (outletId) a la que la key no tiene acceso, responde 403 sin reason. |
| 422 | Hay un dato que corregir. Ver reason. |
| 429 | Pasaste el límite de uso de la key (60 por minuto, 5.000 por día). Esperá un minuto. |
Consultar horarios libres
/v1/integrations/availabilityLos horarios en que se puede agendar uno o varios servicios un día, en una sucursal. Los servicios van seguidos, en el orden pedido. Tiene en cuenta el horario de la sucursal y de cada profesional, sus bloqueos y citas, las unidades libres de lo que pide cada servicio y la anticipación que configuró el comercio: un día fuera de esa ventana viene sin horarios.
Sin userId vale cualquier profesional, y cada horario dice a quién le toca: el libre con menos citas ese día. Es lo que Punto va a elegir si agendás sin nombrar a nadie, salvo que alguien tome ese lugar antes.
Motivos de rechazo (error.details.reason, todos 422): calendar_module_disabled, outlet_unavailable, appointment_invalid, service_unavailable, service_not_offered (nadie de esa sucursal hace el servicio, o el profesional pedido no lo hace).
Parámetros
| Campo | Tipo | Descripción |
|---|---|---|
outletIdobligatorio | string (uuid) | La sucursal. |
itemIdsobligatorio | string | Los servicios, separados por coma, en el orden en que se hacen (hasta 20). |
dateobligatorio | string (date) | El día, AAAA-MM-DD. |
userId | string (uuid) | El profesional, si el cliente eligió uno. Sin él, cualquiera. |
Ejemplo de llamado
Horarios de un servicio
curl "$PUNTO_API/v1/integrations/availability?outletId=1a282724-6073-49c3-8bc3-0114a132e349&itemIds=61230b1e-90e8-4018-ac59-865ca957b293&date=2026-11-10" \
-H "Authorization: Bearer $PUNTO_KEY"Respuesta (dentro de data)
| Campo | Tipo | Descripción |
|---|---|---|
outletId | string (uuid) | |
date | string (date) | |
slotMinutes | integer | Cada cuántos minutos arranca un horario en esta sucursal. |
slots | array<object> | |
slots[].startsAt | string | Hora del comercio, sin zona: AAAA-MM-DD HH:MM:SS. |
slots[].endsAt | string | Hora del comercio, sin zona: AAAA-MM-DD HH:MM:SS. |
slots[].lines | array<object> | Cada servicio, con su profesional y su horario. |
slots[].lines[].itemId | string (uuid) | |
slots[].lines[].userId | string (uuid) | A quién le toca. |
slots[].lines[].userName | string | |
slots[].lines[].startsAt | string | Hora del comercio, sin zona: AAAA-MM-DD HH:MM:SS. |
slots[].lines[].endsAt | string | Hora del comercio, sin zona: AAAA-MM-DD HH:MM:SS. |
Respuesta de ejemplo
200 · Los horarios libres de ese día.
{
"ok": true,
"data": {
"outletId": "1a282724-6073-49c3-8bc3-0114a132e349",
"date": "2026-11-10",
"slotMinutes": 15,
"slots": [
{
"startsAt": "2026-11-10 10:30:00",
"endsAt": "2026-11-10 11:15:00",
"lines": [
{
"itemId": "61230b1e-90e8-4018-ac59-865ca957b293",
"userId": "3e52da17-74a2-49c3-9d07-8d4806671fd5",
"userName": "Ana Gómez",
"startsAt": "2026-11-10 10:30:00",
"endsAt": "2026-11-10 11:15:00"
}
]
}
]
}
}Códigos de respuesta
| 200 | Los horarios libres de ese día. |
| 401 | Falta la key o no es válida. |
| 403 | La key no puede hacer esto. reason: addon_api_required (el comercio no tiene activa la API de integraciones), api_key_scope_missing (la key no es de integración), account_blocked, account_suspended o account_inactive (la cuenta del comercio no está operativa). Si el usuario que emitió la key no tiene permiso para vender, o si pedís una sucursal (outletId) a la que la key no tiene acceso, responde 403 sin reason. |
| 422 | Hay un dato que corregir. Ver reason. |
| 429 | Pasaste el límite de uso de la key (60 por minuto, 5.000 por día). Esperá un minuto. |
Consultar una cita
/v1/integrations/appointmentsUna cita por su id o por tu channelRef: en qué estado está, a qué hora, con quién, y si ya se cobró (charged). manageUrl e icsUrl vienen solo si el usuario dueño de la key puede gestionar la agenda, porque con ese link se puede cancelar la cita. Punto no te avisa de los cambios: consultala cuando lo necesites.
Motivos de rechazo (error.details.reason, 422): calendar_module_disabled.
Parámetros
| Campo | Tipo | Descripción |
|---|---|---|
id | string (uuid) | El identificador de la cita en Punto. Mandá este o channelRef. |
channelRef | string | La referencia que mandaste al agendarla. |
Ejemplo de llamado
Por tu referencia
curl "$PUNTO_API/v1/integrations/appointments?channelRef=BOT-7781" \
-H "Authorization: Bearer $PUNTO_KEY"Respuesta (dentro de data)
| Campo | Tipo | Descripción |
|---|---|---|
id | string (uuid) | |
channelRef | string | null | Tu referencia. Las citas que no agendaste por la API no la tienen. |
outletId | string (uuid) | |
status | string | Reservada, confirmada, presente, en proceso, finalizada, no vino o cancelada. Valores: booked, confirmed, arrived, in_progress, done, no_show, cancelled. |
source | string | Por dónde se agendó. |
version | integer | Sube con cada cambio. Mandala como expectedVersion para no pisar un cambio ajeno. |
startsAt | string | Hora del comercio, sin zona: AAAA-MM-DD HH:MM:SS. |
endsAt | string | Hora del comercio, sin zona: AAAA-MM-DD HH:MM:SS. |
customerId | string (uuid) | null | |
note | string | null | |
cancelReason | string | null | |
charged | boolean | Si ya se cobró. Una cita cobrada no se cambia. |
manageUrl | string | null | La página donde el cliente ve su cita, la cambia de horario o la cancela. Mandásela vos. Con ese link se puede cancelar la cita: null si el usuario dueño de la key no puede gestionar la agenda. |
icsUrl | string | null | El archivo para sumarla a su calendario. null en el mismo caso que manageUrl. |
duplicated | boolean | Solo al agendar: true si esa channelRef ya existía. |
lines | array<object> | Cada servicio de la cita, con su profesional y su horario. |
lines[].itemId | string (uuid) | null | |
lines[].name | string | null | |
lines[].userId | string (uuid) | |
lines[].userName | string | |
lines[].startsAt | string | Hora del comercio, sin zona: AAAA-MM-DD HH:MM:SS. |
lines[].endsAt | string | Hora del comercio, sin zona: AAAA-MM-DD HH:MM:SS. |
lines[].resources | array<object> | Lo que ocupa: de qué tipo y qué unidad. |
lines[].resources[].type | string | |
lines[].resources[].unit | string |
Respuesta de ejemplo
200 · La cita.
{
"ok": true,
"data": {
"id": "5d7c1e42-8a3b-4f6d-9c21-7e4b0a6d2f13",
"channelRef": "BOT-7781",
"outletId": "1a282724-6073-49c3-8bc3-0114a132e349",
"status": "booked",
"source": "api",
"version": 1,
"startsAt": "2026-11-10 10:30:00",
"endsAt": "2026-11-10 11:15:00",
"customerId": "9b2e4c71-0d6a-4f38-b5e2-3a1c8d7e6f90",
"note": null,
"cancelReason": null,
"charged": false,
"manageUrl": "https://app.punto.la/cita/q3Zk8vT1mN0xYpL5sW2aBg",
"icsUrl": "https://app.punto.la/cita/q3Zk8vT1mN0xYpL5sW2aBg/cita.ics",
"lines": [
{
"itemId": "61230b1e-90e8-4018-ac59-865ca957b293",
"name": "Servicio de ejemplo",
"userId": "3e52da17-74a2-49c3-9d07-8d4806671fd5",
"userName": "Ana Gómez",
"startsAt": "2026-11-10 10:30:00",
"endsAt": "2026-11-10 11:15:00",
"resources": [
{
"type": "Sala",
"unit": "Sala 2"
}
]
}
]
}
}Códigos de respuesta
| 200 | La cita. |
| 401 | Falta la key o no es válida. |
| 403 | La key no puede hacer esto. reason: addon_api_required (el comercio no tiene activa la API de integraciones), api_key_scope_missing (la key no es de integración), account_blocked, account_suspended o account_inactive (la cuenta del comercio no está operativa). Si el usuario que emitió la key no tiene permiso para vender, o si pedís una sucursal (outletId) a la que la key no tiene acceso, responde 403 sin reason. |
| 404 | No hay nada con ese identificador en este comercio. |
| 422 | Hay un dato que corregir. Ver reason. |
| 429 | Pasaste el límite de uso de la key (60 por minuto, 5.000 por día). Esperá un minuto. |
Agendar una cita
/v1/integrations/appointmentsAgenda una cita con las mismas reglas que la caja del comercio, y aparece sola en su agenda. Sin userId le toca al profesional libre con menos citas ese día. El mismo channelRef dos veces devuelve la cita que ya existe (duplicated: true).
Si el horario no entra responde 422 con el motivo y error.details.suggestions, los horarios libres más cercanos, con la misma forma que GET /v1/integrations/availability. Un pedido rechazado no crea al cliente.
Cada cita trae manageUrl, la página donde su cliente la ve, la cambia de horario o la cancela, e icsUrl, el archivo para sumarla a su calendario: mandáselos vos.
Motivos de rechazo (error.details.reason, 422 salvo que se indique): calendar_module_disabled, outlet_unavailable, appointment_invalid, booking_too_soon, booking_too_far (fuera de la anticipación que configuró el comercio), service_unavailable, service_not_offered, staff_not_agendable, outside_staff_hours, outside_outlet_hours, overlap (el profesional ya tiene algo a esa hora, una cita o un bloqueo), no_staff_free (pediste cualquier profesional y ninguno está libre), resource_busy (no hay una unidad libre de lo que pide el servicio), customer_not_found, customer_name_required, customer_invalid, channel_ref_taken (409: esa referencia ya la usó otra cita de una sucursal a la que la key no tiene acceso).
Cuerpo de la petición
| Campo | Tipo | Descripción |
|---|---|---|
channelRefobligatorio | string | El identificador de la cita en tu sistema. Es lo que evita que se agende dos veces. Hasta 45 caracteres. |
outletIdobligatorio | string (uuid) | La sucursal. |
itemIdsobligatorio | array<string (uuid)> | Los servicios, en el orden en que se hacen. Van seguidos. Hasta 20 elementos. |
userId | string (uuid) | El profesional. Sin él, el libre con menos citas ese día. |
startsAtobligatorio | string | Hora del comercio, sin zona: AAAA-MM-DD HH:MM. |
customer | object | El comprador. Nombralo con externalId o id si ya existe en Punto, o mandá sus datos para que Punto lo busque y, si no lo encuentra, lo cree (ver Clientes). Sin comprador, la venta sale a consumidor final. |
customer.externalId | string | Identificador del cliente en tu sistema. Hasta 64 caracteres. |
customer.id | string (uuid) | Identificador del cliente en Punto. |
customer.document | string | Documento con el que se le factura. Para guardar cédula y RUC a la vez usá personalDocument y fiscalDocument. Hasta 30 caracteres. |
customer.documentType | string | fiscal (en Paraguay, RUC) o personal (en Paraguay, cédula). Sin él, Punto lo deduce del formato. Valores: fiscal, personal. |
customer.personalDocument | string | Cédula (documento personal) de la persona. Va junto al RUC en la misma ficha: mandá los dos cuando los tengas. |
customer.fiscalDocument | string | RUC (documento fiscal) con el que se factura, junto a fiscalName. Va junto a la cédula en la misma ficha. |
customer.name | string | Nombre y apellido. Hace falta (o fiscalName) para crear un cliente nuevo. Hasta 120 caracteres. |
customer.fiscalName | string | Razón social para la factura, si es distinta del nombre. Hasta 160 caracteres. |
customer.email | string (email) | |
customer.phone | string | Con código de país, en formato internacional (por ejemplo +595981000111). |
note | string | Nota de la cita. Hasta 500 caracteres. |
Ejemplo de llamado
Cita con cualquier profesional
curl -X POST "$PUNTO_API/v1/integrations/appointments" \
-H "Authorization: Bearer $PUNTO_KEY" \
-H "Content-Type: application/json" \
-d '{
"channelRef": "BOT-7781",
"outletId": "1a282724-6073-49c3-8bc3-0114a132e349",
"itemIds": ["61230b1e-90e8-4018-ac59-865ca957b293"],
"startsAt": "2026-11-10 10:30",
"customer": { "phone": "+595981123456", "name": "María Pérez" }
}'Respuesta (dentro de data)
| Campo | Tipo | Descripción |
|---|---|---|
id | string (uuid) | |
channelRef | string | null | Tu referencia. Las citas que no agendaste por la API no la tienen. |
outletId | string (uuid) | |
status | string | Reservada, confirmada, presente, en proceso, finalizada, no vino o cancelada. Valores: booked, confirmed, arrived, in_progress, done, no_show, cancelled. |
source | string | Por dónde se agendó. |
version | integer | Sube con cada cambio. Mandala como expectedVersion para no pisar un cambio ajeno. |
startsAt | string | Hora del comercio, sin zona: AAAA-MM-DD HH:MM:SS. |
endsAt | string | Hora del comercio, sin zona: AAAA-MM-DD HH:MM:SS. |
customerId | string (uuid) | null | |
note | string | null | |
cancelReason | string | null | |
charged | boolean | Si ya se cobró. Una cita cobrada no se cambia. |
manageUrl | string | null | La página donde el cliente ve su cita, la cambia de horario o la cancela. Mandásela vos. Con ese link se puede cancelar la cita: null si el usuario dueño de la key no puede gestionar la agenda. |
icsUrl | string | null | El archivo para sumarla a su calendario. null en el mismo caso que manageUrl. |
duplicated | boolean | Solo al agendar: true si esa channelRef ya existía. |
lines | array<object> | Cada servicio de la cita, con su profesional y su horario. |
lines[].itemId | string (uuid) | null | |
lines[].name | string | null | |
lines[].userId | string (uuid) | |
lines[].userName | string | |
lines[].startsAt | string | Hora del comercio, sin zona: AAAA-MM-DD HH:MM:SS. |
lines[].endsAt | string | Hora del comercio, sin zona: AAAA-MM-DD HH:MM:SS. |
lines[].resources | array<object> | Lo que ocupa: de qué tipo y qué unidad. |
lines[].resources[].type | string | |
lines[].resources[].unit | string |
Respuesta de ejemplo
200 · La cita ya estaba registrada (duplicated: true).
{
"ok": true,
"data": {
"id": "5d7c1e42-8a3b-4f6d-9c21-7e4b0a6d2f13",
"channelRef": "BOT-7781",
"outletId": "1a282724-6073-49c3-8bc3-0114a132e349",
"status": "booked",
"source": "api",
"version": 1,
"startsAt": "2026-11-10 10:30:00",
"endsAt": "2026-11-10 11:15:00",
"customerId": "9b2e4c71-0d6a-4f38-b5e2-3a1c8d7e6f90",
"note": null,
"cancelReason": null,
"charged": false,
"manageUrl": "https://app.punto.la/cita/q3Zk8vT1mN0xYpL5sW2aBg",
"icsUrl": "https://app.punto.la/cita/q3Zk8vT1mN0xYpL5sW2aBg/cita.ics",
"lines": [
{
"itemId": "61230b1e-90e8-4018-ac59-865ca957b293",
"name": "Servicio de ejemplo",
"userId": "3e52da17-74a2-49c3-9d07-8d4806671fd5",
"userName": "Ana Gómez",
"startsAt": "2026-11-10 10:30:00",
"endsAt": "2026-11-10 11:15:00",
"resources": [
{
"type": "Sala",
"unit": "Sala 2"
}
]
}
],
"duplicated": true
}
}201 · La cita se agendó.
{
"ok": true,
"data": {
"id": "5d7c1e42-8a3b-4f6d-9c21-7e4b0a6d2f13",
"channelRef": "BOT-7781",
"outletId": "1a282724-6073-49c3-8bc3-0114a132e349",
"status": "booked",
"source": "api",
"version": 1,
"startsAt": "2026-11-10 10:30:00",
"endsAt": "2026-11-10 11:15:00",
"customerId": "9b2e4c71-0d6a-4f38-b5e2-3a1c8d7e6f90",
"note": null,
"cancelReason": null,
"charged": false,
"manageUrl": "https://app.punto.la/cita/q3Zk8vT1mN0xYpL5sW2aBg",
"icsUrl": "https://app.punto.la/cita/q3Zk8vT1mN0xYpL5sW2aBg/cita.ics",
"lines": [
{
"itemId": "61230b1e-90e8-4018-ac59-865ca957b293",
"name": "Servicio de ejemplo",
"userId": "3e52da17-74a2-49c3-9d07-8d4806671fd5",
"userName": "Ana Gómez",
"startsAt": "2026-11-10 10:30:00",
"endsAt": "2026-11-10 11:15:00",
"resources": [
{
"type": "Sala",
"unit": "Sala 2"
}
]
}
],
"duplicated": false
}
}Códigos de respuesta
| 200 | La cita ya estaba registrada (duplicated: true). |
| 201 | La cita se agendó. |
| 401 | Falta la key o no es válida. |
| 403 | La key no puede hacer esto. reason: addon_api_required (el comercio no tiene activa la API de integraciones), api_key_scope_missing (la key no es de integración), account_blocked, account_suspended o account_inactive (la cuenta del comercio no está operativa). Si el usuario que emitió la key no tiene permiso para vender, o si pedís una sucursal (outletId) a la que la key no tiene acceso, responde 403 sin reason. |
| 409 | El estado del comercio impide la operación. No reintentes igual: ver reason. |
| 422 | Hay un dato que corregir. Ver reason. |
| 429 | Pasaste el límite de uso de la key (60 por minuto, 5.000 por día). Esperá un minuto. |
Reprogramar o cancelar una cita
/v1/integrations/appointmentsCon startsAt la cambia de horario: cada servicio conserva su duración y su profesional, salvo que mandes userId. Con status: cancelled la cancela, con cancelReason si querés. Una de las dos.
Si mandás expectedVersion y alguien la cambió desde que la leíste, responde 409 en vez de pisar el cambio. Cancelar dos veces no es un error.
Motivos de rechazo (error.details.reason, 422 salvo que se indique): calendar_module_disabled, appointment_invalid, booking_too_soon, booking_too_far (fuera de la anticipación que configuró el comercio), service_unavailable, service_not_offered, staff_not_agendable, outside_staff_hours, outside_outlet_hours, overlap (el profesional ya tiene algo a esa hora, una cita o un bloqueo), no_staff_free (pediste cualquier profesional y ninguno está libre), resource_busy (no hay una unidad libre de lo que pide el servicio), appointment_closed (409: ya se cobró, se canceló o terminó), appointment_changed (409: cambió desde que la leíste), appointment_not_found (404: se borró mientras tanto).
Parámetros
| Campo | Tipo | Descripción |
|---|---|---|
id | string (uuid) | El identificador de la cita en Punto. Mandá este o channelRef. |
channelRef | string | La referencia que mandaste al agendarla. |
Cuerpo de la petición
| Campo | Tipo | Descripción |
|---|---|---|
startsAt | string | El horario nuevo, hora del comercio, sin zona: AAAA-MM-DD HH:MM. |
userId | string (uuid) | Para cambiar de profesional al reprogramar. Sin él, cada servicio sigue con el suyo. |
status | string | Para cancelarla. Valores: cancelled. |
cancelReason | string | Hasta 500 caracteres. |
expectedVersion | integer | La version que leíste. Si cambió desde entonces, responde 409 en vez de pisar el cambio. |
Ejemplo de llamado
Cambiar de horario
curl -X PATCH "$PUNTO_API/v1/integrations/appointments?channelRef=BOT-7781" \
-H "Authorization: Bearer $PUNTO_KEY" \
-H "Content-Type: application/json" \
-d '{ "startsAt": "2026-11-11 16:00" }'Respuesta (dentro de data)
| Campo | Tipo | Descripción |
|---|---|---|
id | string (uuid) | |
channelRef | string | null | Tu referencia. Las citas que no agendaste por la API no la tienen. |
outletId | string (uuid) | |
status | string | Reservada, confirmada, presente, en proceso, finalizada, no vino o cancelada. Valores: booked, confirmed, arrived, in_progress, done, no_show, cancelled. |
source | string | Por dónde se agendó. |
version | integer | Sube con cada cambio. Mandala como expectedVersion para no pisar un cambio ajeno. |
startsAt | string | Hora del comercio, sin zona: AAAA-MM-DD HH:MM:SS. |
endsAt | string | Hora del comercio, sin zona: AAAA-MM-DD HH:MM:SS. |
customerId | string (uuid) | null | |
note | string | null | |
cancelReason | string | null | |
charged | boolean | Si ya se cobró. Una cita cobrada no se cambia. |
manageUrl | string | null | La página donde el cliente ve su cita, la cambia de horario o la cancela. Mandásela vos. Con ese link se puede cancelar la cita: null si el usuario dueño de la key no puede gestionar la agenda. |
icsUrl | string | null | El archivo para sumarla a su calendario. null en el mismo caso que manageUrl. |
duplicated | boolean | Solo al agendar: true si esa channelRef ya existía. |
lines | array<object> | Cada servicio de la cita, con su profesional y su horario. |
lines[].itemId | string (uuid) | null | |
lines[].name | string | null | |
lines[].userId | string (uuid) | |
lines[].userName | string | |
lines[].startsAt | string | Hora del comercio, sin zona: AAAA-MM-DD HH:MM:SS. |
lines[].endsAt | string | Hora del comercio, sin zona: AAAA-MM-DD HH:MM:SS. |
lines[].resources | array<object> | Lo que ocupa: de qué tipo y qué unidad. |
lines[].resources[].type | string | |
lines[].resources[].unit | string |
Respuesta de ejemplo
200 · La cita, ya guardada.
{
"ok": true,
"data": {
"id": "5d7c1e42-8a3b-4f6d-9c21-7e4b0a6d2f13",
"channelRef": "BOT-7781",
"outletId": "1a282724-6073-49c3-8bc3-0114a132e349",
"status": "booked",
"source": "api",
"version": 2,
"startsAt": "2026-11-11 16:00:00",
"endsAt": "2026-11-11 16:45:00",
"customerId": "9b2e4c71-0d6a-4f38-b5e2-3a1c8d7e6f90",
"note": null,
"cancelReason": null,
"charged": false,
"manageUrl": "https://app.punto.la/cita/q3Zk8vT1mN0xYpL5sW2aBg",
"icsUrl": "https://app.punto.la/cita/q3Zk8vT1mN0xYpL5sW2aBg/cita.ics",
"lines": [
{
"itemId": "61230b1e-90e8-4018-ac59-865ca957b293",
"name": "Servicio de ejemplo",
"userId": "3e52da17-74a2-49c3-9d07-8d4806671fd5",
"userName": "Ana Gómez",
"startsAt": "2026-11-10 10:30:00",
"endsAt": "2026-11-10 11:15:00",
"resources": [
{
"type": "Sala",
"unit": "Sala 2"
}
]
}
]
}
}Códigos de respuesta
| 200 | La cita, ya guardada. |
| 401 | Falta la key o no es válida. |
| 403 | La key no puede hacer esto. reason: addon_api_required (el comercio no tiene activa la API de integraciones), api_key_scope_missing (la key no es de integración), account_blocked, account_suspended o account_inactive (la cuenta del comercio no está operativa). Si el usuario que emitió la key no tiene permiso para vender, o si pedís una sucursal (outletId) a la que la key no tiene acceso, responde 403 sin reason. |
| 404 | No hay nada con ese identificador en este comercio. |
| 409 | El estado del comercio impide la operación. No reintentes igual: ver reason. |
| 422 | Hay un dato que corregir. Ver reason. |
| 429 | Pasaste el límite de uso de la key (60 por minuto, 5.000 por día). Esperá un minuto. |
Ventas
Pedidos cobrados, que se registran y se facturan en el acto.
Consultar una venta
/v1/integrations/salesDevuelve la venta registrada para un pedido y el estado de su factura electrónica. Consultala hasta que einvoice.sifenVerdict deje de ser pending (ver Factura electrónica y KuDE).
Parámetros
| Campo | Tipo | Descripción |
|---|---|---|
orderIdobligatorio | string | El identificador del pedido en tu sistema, el mismo que mandaste al registrarlo. |
Ejemplo de llamado
Estado fiscal
curl "$PUNTO_API/v1/integrations/sales?orderId=WEB-10245" \
-H "Authorization: Bearer $PUNTO_KEY"Respuesta (dentro de data)
| Campo | Tipo | Descripción |
|---|---|---|
orderId | string | Tu identificador del pedido. |
uid | string | Identificador de la venta en Punto, derivado de tu orderId. |
transactionId | string (uuid) | Identificador de la venta en Punto. |
duplicated | boolean | Solo en el alta: true si el pedido ya estaba registrado. |
document | object | null | El comprobante de la venta. |
document.number | string | null | Número de comprobante con su prefijo, listo para mostrar. |
document.invoiceNo | integer | null | El número, sin prefijo. |
document.prefix | string | null | Punto de expedición (ver Glosario). |
document.serie | string | null | |
document.timbrado | string | null | Timbrado con el que se numeró. |
document.date | string | null | Fecha y hora de la venta. |
document.total | number | null | |
einvoice | object | Estado de la factura electrónica. Ver Factura electrónica y KuDE. |
einvoice.issued | boolean | true solo si fue aprobada. |
einvoice.status | string | null | Estado del envío. null: la caja no emite factura electrónica. Valores: pending, sending, issued, error, cancelled, skipped. |
einvoice.sifenVerdict | string | null | Resultado de la validación fiscal (en Paraguay, SIFEN). Valores: pending, approved, rejected. |
einvoice.sifenReason | string | null | Motivo del rechazo. |
einvoice.cdc | string | null | Código de control del documento. |
einvoice.documentNumber | string | null | |
einvoice.portalUrl | string (uri) | null | Link donde el comprador descarga su KuDE. |
einvoice.qrUrl | string (uri) | null | Link de consulta pública del documento, para imprimir su QR. null mientras el documento no se aprobó. |
order | object | Solo con fromOrderId: la orden facturada. Queda cerrada si ya estaba entregada; si no, se cierra al entregarse. |
order.orderId | string | Tu identificador del pedido. |
order.id | string (uuid) | Identificador de la orden en Punto. |
order.number | integer | null | Número de la orden en la sucursal (el que ve el equipo). |
order.status | string | pending: pendiente (el comercio todavía no decidió); sent: en espera (aceptada, nadie la empezó); in_progress: en proceso; ready: lista; out_for_delivery: en camino; delivered: entregada; closed: facturada y entregada; cancelled: cancelada o rechazada. Valores: pending, open, sent, in_progress, ready, out_for_delivery, delivered, closed, cancelled. |
order.fulfillment | string | Valores: delivery, takeaway, dine_in. |
order.scheduledFor | string | null | |
order.createdAt | string | |
order.registerId | string (uuid) | null | |
order.customerId | string (uuid) | null | Identificador del comprador en Punto. |
order.delivery | object | null | La dirección con la que se registró la orden. |
order.delivery.address | string | null | |
order.delivery.reference | string | null | |
order.delivery.lat | number | null | |
order.delivery.lng | number | null | |
order.total | number | Total de la orden según sus líneas. |
order.acceptance | object | Qué decidió el comercio sobre el pedido. status sigue el ciclo completo de la orden; esto contesta si se aceptó. |
order.acceptance.status | string | pending: todavía no decidió; accepted: la aceptó; rejected: la rechazó (la orden queda cancelled). Valores: pending, accepted, rejected. |
order.acceptance.decidedAt | string | null | Cuándo la aceptó o rechazó. |
order.acceptance.etaMinutes | integer | null | Minutos que el comercio estimó al aceptar (opcional). |
order.acceptance.estimatedReadyAt | string | null | Cuándo estaría lista. Viene en toda orden aceptada; null mientras está pendiente o si se rechazó. |
order.acceptance.reason | string | null | Motivo del rechazo. Valores: out_of_stock, closed, too_busy, out_of_area, other. |
order.acceptance.reasonLabel | string | null | El motivo en palabras, para mostrarle al comprador. |
order.acceptance.note | string | null | Detalle que escribió el comercio al rechazar. |
order.trackingUrl | string (uri) | null | Link público de seguimiento para tu comprador. Muestra la etapa del pedido, nunca los productos, los montos, la dirección ni el nombre. Deja de responder 12 horas después del último movimiento. |
Respuesta de ejemplo
200 · La venta y su estado fiscal. En el ejemplo, la factura ya fue aprobada.
{
"ok": true,
"data": {
"orderId": "WEB-10245",
"uid": "ecom:WEB-10245",
"transactionId": "c3f1a7d2-8e45-4b90-a6c1-7d2e9f0b4a58",
"document": {
"number": "001-003-0000154",
"invoiceNo": 154,
"prefix": "001-003",
"serie": null,
"timbrado": "16234567",
"date": "2026-10-03 10:20:05",
"total": 105000
},
"einvoice": {
"issued": true,
"status": "issued",
"sifenVerdict": "approved",
"sifenReason": null,
"cdc": "01800123456001003000015412026100312345678901",
"documentNumber": "001-003-0000154",
"portalUrl": "https://app.punto.la/factura/eyJ0IjoiYzNmMWE3ZDIifQ.Xb2k",
"qrUrl": "https://ekuatia.set.gov.py/consultas/qr?nVersion=150&Id=01800123456001003000015412026100312345678901"
}
}
}Códigos de respuesta
| 200 | La venta y su estado fiscal. En el ejemplo, la factura ya fue aprobada. |
| 401 | Falta la key o no es válida. |
| 403 | La key no puede hacer esto. reason: addon_api_required (el comercio no tiene activa la API de integraciones), api_key_scope_missing (la key no es de integración), account_blocked, account_suspended o account_inactive (la cuenta del comercio no está operativa). Si el usuario que emitió la key no tiene permiso para vender, o si pedís una sucursal (outletId) a la que la key no tiene acceso, responde 403 sin reason. |
| 404 | No hay nada con ese identificador en este comercio. |
| 422 | Hay un dato que corregir. Ver reason. |
| 429 | Pasaste el límite de uso de la key (60 por minuto, 5.000 por día). Esperá un minuto. |
Registrar una venta
/v1/integrations/salesRegistra un pedido cobrado como venta: descuenta stock, calcula los impuestos, numera el comprobante y, si la caja factura electrónicamente, emite la factura.
Con fromOrderId, factura una orden que entró antes por POST /v1/integrations/orders y la cierra.
Motivos de rechazo (error.details.reason): register_unavailable, item_unavailable, addons_invalid, price_below_addons, total_mismatch, tax_unavailable, payment_method_unknown, payment_method_cash, credit_requires_customer, credit_not_enabled, customer_not_found, customer_name_required, customer_invalid, order_not_found, order_outlet_mismatch, register_in_use_by_device (la caja la usa un dispositivo del comercio: pedí una caja aparte para tu sistema) (422); order_pending_acceptance, order_rejected, order_cancelled, order_already_invoiced, invoice_range_exhausted, invoice_number_taken (409). Un timbrado vencido responde 422 con details.code: invoice_auth_expired: el comercio tiene que cargar uno vigente.
Cuerpo de la petición
| Campo | Tipo | Descripción |
|---|---|---|
orderIdobligatorio | string | Identificador del pedido en tu sistema. Ver Idempotencia. Hasta 45 caracteres. |
registerIdobligatorio | string (uuid) | La caja contra la que se registra (GET /v1/integrations/registers). |
itemsobligatorio | array<object> | Hasta 200 elementos. |
items[].itemId | string (uuid) | Identificador del artículo en Punto. |
items[].sku | string | Código del artículo, si no mandás itemId. Hasta 120 caracteres. |
items[].quantityobligatorio | number | |
items[].unitPriceobligatorio | number | Precio por unidad que cobró tu sistema, con los agregados incluidos. |
items[].note | string | Nota de la línea (en una orden, la ve el equipo que la prepara). Hasta 200 caracteres. |
items[].addons | array<object> | Agregados del artículo (opciones de sus grupos). El recargo lo pone el catálogo. Hasta 50 elementos. |
items[].addons[].optionIdobligatorio | string (uuid) | La opción, de los addonGroups del artículo en el catálogo. |
items[].addons[].quantity | integer | Por defecto: 1. |
charges | array<object> | Hasta 200 elementos. |
charges[].descriptionobligatorio | string | Hasta 120 caracteres. |
charges[].amountobligatorio | number | |
charges[].taxId | string (uuid) | Impuesto del cargo (GET /v1/taxes). Sin él, el predeterminado del comercio. |
totalobligatorio | number | Lo que cobraste al comprador. Se compara con lo que suman las líneas. |
payment | object | Obligatorio al contado; no va en una venta a crédito. |
payment.methodobligatorio | string | Medio de pago del comercio: su id de GET /v1/payment-methods (también se acepta su código o su nombre). El efectivo se rechaza: una venta online no se cobra en el cajón del comercio. Hasta 120 caracteres. |
payment.reference | string | Referencia del cobro en tu pasarela, para conciliar. Hasta 120 caracteres. |
customer | object | El comprador. Nombralo con externalId o id si ya existe en Punto, o mandá sus datos para que Punto lo busque y, si no lo encuentra, lo cree (ver Clientes). Sin comprador, la venta sale a consumidor final. |
customer.externalId | string | Identificador del cliente en tu sistema. Hasta 64 caracteres. |
customer.id | string (uuid) | Identificador del cliente en Punto. |
customer.document | string | Documento con el que se le factura. Para guardar cédula y RUC a la vez usá personalDocument y fiscalDocument. Hasta 30 caracteres. |
customer.documentType | string | fiscal (en Paraguay, RUC) o personal (en Paraguay, cédula). Sin él, Punto lo deduce del formato. Valores: fiscal, personal. |
customer.personalDocument | string | Cédula (documento personal) de la persona. Va junto al RUC en la misma ficha: mandá los dos cuando los tengas. |
customer.fiscalDocument | string | RUC (documento fiscal) con el que se factura, junto a fiscalName. Va junto a la cédula en la misma ficha. |
customer.name | string | Nombre y apellido. Hace falta (o fiscalName) para crear un cliente nuevo. Hasta 120 caracteres. |
customer.fiscalName | string | Razón social para la factura, si es distinta del nombre. Hasta 160 caracteres. |
customer.email | string (email) | |
customer.phone | string | Con código de país, en formato internacional (por ejemplo +595981000111). |
note | string | Hasta 500 caracteres. |
credit | boolean | Venta a crédito: sin payment, con dueDate y un comprador que tenga crédito habilitado en Punto. Por defecto: false. |
dueDate | string (date) | Vencimiento de una venta a crédito. |
issuedAt | string (date-time) | Momento de la venta. Sin él, el de la llamada. |
fromOrderId | string | orderId de una orden registrada con POST /v1/integrations/orders que esta venta factura. La orden tiene que ser de la misma sucursal que la caja. Hasta 45 caracteres. |
Ejemplo de llamado
Venta cobrada con envío
curl -X POST "$PUNTO_API/v1/integrations/sales" \
-H "Authorization: Bearer $PUNTO_KEY" \
-H "Content-Type: application/json" \
-d '{
"orderId": "WEB-10245",
"registerId": "81c541da-640e-4891-a1a0-b32841e64c75",
"items": [
{ "sku": "CAFE-250", "quantity": 2, "unitPrice": 45000 }
],
"charges": [
{ "description": "Envío a domicilio", "amount": 15000 }
],
"total": 105000,
"payment": { "method": "e7a1c3d5-2b49-4f60-8d17-5c9e0a3b6f24", "reference": "psp_7f3a91" },
"customer": { "externalId": "cliente-4812" }
}'Facturar una orden
curl -X POST "$PUNTO_API/v1/integrations/sales" \
-H "Authorization: Bearer $PUNTO_KEY" \
-H "Content-Type: application/json" \
-d '{
"orderId": "WEB-10246",
"fromOrderId": "WEB-10246",
"registerId": "81c541da-640e-4891-a1a0-b32841e64c75",
"items": [
{ "sku": "HAMB-CLASICA", "quantity": 1, "unitPrice": 42000,
"addons": [ { "optionId": "c1a2b3c4-d5e6-4f70-8a91-b2c3d4e5f604", "quantity": 1 } ] }
],
"charges": [ { "description": "Envío a domicilio", "amount": 10000 } ],
"total": 52000,
"payment": { "method": "e7a1c3d5-2b49-4f60-8d17-5c9e0a3b6f25" }
}'Respuesta (dentro de data)
| Campo | Tipo | Descripción |
|---|---|---|
orderId | string | Tu identificador del pedido. |
uid | string | Identificador de la venta en Punto, derivado de tu orderId. |
transactionId | string (uuid) | Identificador de la venta en Punto. |
duplicated | boolean | Solo en el alta: true si el pedido ya estaba registrado. |
document | object | null | El comprobante de la venta. |
document.number | string | null | Número de comprobante con su prefijo, listo para mostrar. |
document.invoiceNo | integer | null | El número, sin prefijo. |
document.prefix | string | null | Punto de expedición (ver Glosario). |
document.serie | string | null | |
document.timbrado | string | null | Timbrado con el que se numeró. |
document.date | string | null | Fecha y hora de la venta. |
document.total | number | null | |
einvoice | object | Estado de la factura electrónica. Ver Factura electrónica y KuDE. |
einvoice.issued | boolean | true solo si fue aprobada. |
einvoice.status | string | null | Estado del envío. null: la caja no emite factura electrónica. Valores: pending, sending, issued, error, cancelled, skipped. |
einvoice.sifenVerdict | string | null | Resultado de la validación fiscal (en Paraguay, SIFEN). Valores: pending, approved, rejected. |
einvoice.sifenReason | string | null | Motivo del rechazo. |
einvoice.cdc | string | null | Código de control del documento. |
einvoice.documentNumber | string | null | |
einvoice.portalUrl | string (uri) | null | Link donde el comprador descarga su KuDE. |
einvoice.qrUrl | string (uri) | null | Link de consulta pública del documento, para imprimir su QR. null mientras el documento no se aprobó. |
order | object | Solo con fromOrderId: la orden facturada. Queda cerrada si ya estaba entregada; si no, se cierra al entregarse. |
order.orderId | string | Tu identificador del pedido. |
order.id | string (uuid) | Identificador de la orden en Punto. |
order.number | integer | null | Número de la orden en la sucursal (el que ve el equipo). |
order.status | string | pending: pendiente (el comercio todavía no decidió); sent: en espera (aceptada, nadie la empezó); in_progress: en proceso; ready: lista; out_for_delivery: en camino; delivered: entregada; closed: facturada y entregada; cancelled: cancelada o rechazada. Valores: pending, open, sent, in_progress, ready, out_for_delivery, delivered, closed, cancelled. |
order.fulfillment | string | Valores: delivery, takeaway, dine_in. |
order.scheduledFor | string | null | |
order.createdAt | string | |
order.registerId | string (uuid) | null | |
order.customerId | string (uuid) | null | Identificador del comprador en Punto. |
order.delivery | object | null | La dirección con la que se registró la orden. |
order.delivery.address | string | null | |
order.delivery.reference | string | null | |
order.delivery.lat | number | null | |
order.delivery.lng | number | null | |
order.total | number | Total de la orden según sus líneas. |
order.acceptance | object | Qué decidió el comercio sobre el pedido. status sigue el ciclo completo de la orden; esto contesta si se aceptó. |
order.acceptance.status | string | pending: todavía no decidió; accepted: la aceptó; rejected: la rechazó (la orden queda cancelled). Valores: pending, accepted, rejected. |
order.acceptance.decidedAt | string | null | Cuándo la aceptó o rechazó. |
order.acceptance.etaMinutes | integer | null | Minutos que el comercio estimó al aceptar (opcional). |
order.acceptance.estimatedReadyAt | string | null | Cuándo estaría lista. Viene en toda orden aceptada; null mientras está pendiente o si se rechazó. |
order.acceptance.reason | string | null | Motivo del rechazo. Valores: out_of_stock, closed, too_busy, out_of_area, other. |
order.acceptance.reasonLabel | string | null | El motivo en palabras, para mostrarle al comprador. |
order.acceptance.note | string | null | Detalle que escribió el comercio al rechazar. |
order.trackingUrl | string (uri) | null | Link público de seguimiento para tu comprador. Muestra la etapa del pedido, nunca los productos, los montos, la dirección ni el nombre. Deja de responder 12 horas después del último movimiento. |
Respuesta de ejemplo
200 · El pedido ya estaba registrado (duplicated: true): es la venta original.
{
"ok": true,
"data": {
"orderId": "WEB-10245",
"uid": "ecom:WEB-10245",
"transactionId": "c3f1a7d2-8e45-4b90-a6c1-7d2e9f0b4a58",
"duplicated": true,
"document": {
"number": "001-003-0000154",
"invoiceNo": 154,
"prefix": "001-003",
"serie": null,
"timbrado": "16234567",
"date": "2026-10-03 10:20:05",
"total": 105000
},
"einvoice": {
"issued": false,
"status": "sending",
"sifenVerdict": "pending",
"sifenReason": null,
"cdc": null,
"documentNumber": "001-003-0000154",
"portalUrl": "https://app.punto.la/factura/eyJ0IjoiYzNmMWE3ZDIifQ.Xb2k",
"qrUrl": null
}
}
}201 · Venta registrada. La factura electrónica todavía se está validando.
{
"ok": true,
"data": {
"orderId": "WEB-10245",
"uid": "ecom:WEB-10245",
"transactionId": "c3f1a7d2-8e45-4b90-a6c1-7d2e9f0b4a58",
"duplicated": false,
"document": {
"number": "001-003-0000154",
"invoiceNo": 154,
"prefix": "001-003",
"serie": null,
"timbrado": "16234567",
"date": "2026-10-03 10:20:05",
"total": 105000
},
"einvoice": {
"issued": false,
"status": "sending",
"sifenVerdict": "pending",
"sifenReason": null,
"cdc": null,
"documentNumber": "001-003-0000154",
"portalUrl": "https://app.punto.la/factura/eyJ0IjoiYzNmMWE3ZDIifQ.Xb2k",
"qrUrl": null
}
}
}Códigos de respuesta
| 200 | El pedido ya estaba registrado (duplicated: true): es la venta original. |
| 201 | Venta registrada. La factura electrónica todavía se está validando. |
| 401 | Falta la key o no es válida. |
| 403 | La key no puede hacer esto. reason: addon_api_required (el comercio no tiene activa la API de integraciones), api_key_scope_missing (la key no es de integración), account_blocked, account_suspended o account_inactive (la cuenta del comercio no está operativa). Si el usuario que emitió la key no tiene permiso para vender, o si pedís una sucursal (outletId) a la que la key no tiene acceso, responde 403 sin reason. |
| 409 | El estado del comercio impide la operación. No reintentes igual: ver reason. |
| 422 | Hay un dato que corregir. Ver reason. |
| 429 | Pasaste el límite de uso de la key (60 por minuto, 5.000 por día). Esperá un minuto. |
Datos del comercio
Cajas, catálogo, stock, medios de pago e impuestos, para armar pedidos válidos.
Listar las cajas
/v1/integrations/registersLas cajas que tu key puede usar en un pedido: activas, de sucursales activas y de las sucursales a las que tiene acceso el usuario que emitió la key. Es el primer llamado para probar una key nueva.
Ejemplo de llamado
Cajas disponibles
curl "$PUNTO_API/v1/integrations/registers" \
-H "Authorization: Bearer $PUNTO_KEY"Respuesta (dentro de data)
| Campo | Tipo | Descripción |
|---|---|---|
registers | array<object> | |
registers[].registerId | string (uuid) | |
registers[].name | string | |
registers[].outletId | string (uuid) | La sucursal de la caja. |
registers[].outletName | string | |
registers[].electronicInvoice | boolean | Si una venta registrada en esta caja sale con factura electrónica. |
registers[].available | boolean | Si tu sistema puede usar esta caja. false: la usa un dispositivo del comercio y la venta responde 422 con register_in_use_by_device. |
Respuesta de ejemplo
200 · Las cajas.
{
"ok": true,
"data": {
"registers": [
{
"registerId": "81c541da-640e-4891-a1a0-b32841e64c75",
"name": "Tienda online",
"outletId": "1a282724-6073-49c3-8bc3-0114a132e349",
"outletName": "Casa central",
"electronicInvoice": true,
"available": true
},
{
"registerId": "81c541da-640e-4891-a1a0-b32841e64c76",
"name": "Mostrador",
"outletId": "1a282724-6073-49c3-8bc3-0114a132e349",
"outletName": "Casa central",
"electronicInvoice": true,
"available": false
}
]
}
}Códigos de respuesta
| 200 | Las cajas. |
| 401 | Falta la key o no es válida. |
| 403 | La key no puede hacer esto. reason: addon_api_required (el comercio no tiene activa la API de integraciones), api_key_scope_missing (la key no es de integración), account_blocked, account_suspended o account_inactive (la cuenta del comercio no está operativa). Si el usuario que emitió la key no tiene permiso para vender, o si pedís una sucursal (outletId) a la que la key no tiene acceso, responde 403 sin reason. |
| 429 | Pasaste el límite de uso de la key (60 por minuto, 5.000 por día). Esperá un minuto. |
Consultar stock
/v1/integrations/stockCuántas unidades hay de cada artículo en una sucursal, hasta 200 artículos por consulta. Es el mismo saldo que ve el comercio en el local. Responde 403 si la key no tiene acceso a esa sucursal.
Parámetros
| Campo | Tipo | Descripción |
|---|---|---|
outletIdobligatorio | string (uuid) | La sucursal (outletId de GET /v1/integrations/registers). |
itemIdsobligatorio | string | Los artículos, separados por coma (hasta 200). |
Ejemplo de llamado
Stock de dos artículos
curl "$PUNTO_API/v1/integrations/stock?outletId=1a282724-6073-49c3-8bc3-0114a132e349&itemIds=10223f3b-2e3d-4339-8496-9f288d8be65b,61230b1e-90e8-4018-ac59-865ca957b293" \
-H "Authorization: Bearer $PUNTO_KEY"Respuesta (dentro de data)
| Campo | Tipo | Descripción |
|---|---|---|
outletId | string (uuid) | |
items | array<object> | Un artículo que no existe en el comercio no viene. |
items[].itemId | string (uuid) | |
items[].tracksStock | boolean | Si el comercio controla el stock de este artículo. Si no, se vende sin límite. |
items[].quantity | number | null | Unidades en la sucursal. Puede ser negativa (se vendió más de lo cargado). null si el artículo no controla stock. |
Respuesta de ejemplo
200 · El saldo de cada artículo.
{
"ok": true,
"data": {
"outletId": "1a282724-6073-49c3-8bc3-0114a132e349",
"items": [
{
"itemId": "10223f3b-2e3d-4339-8496-9f288d8be65b",
"tracksStock": true,
"quantity": 34
},
{
"itemId": "61230b1e-90e8-4018-ac59-865ca957b293",
"tracksStock": false,
"quantity": null
}
]
}
}Códigos de respuesta
| 200 | El saldo de cada artículo. |
| 401 | Falta la key o no es válida. |
| 403 | La key no puede hacer esto. reason: addon_api_required (el comercio no tiene activa la API de integraciones), api_key_scope_missing (la key no es de integración), account_blocked, account_suspended o account_inactive (la cuenta del comercio no está operativa). Si el usuario que emitió la key no tiene permiso para vender, o si pedís una sucursal (outletId) a la que la key no tiene acceso, responde 403 sin reason. |
| 422 | Hay un dato que corregir. Ver reason. |
| 429 | Pasaste el límite de uso de la key (60 por minuto, 5.000 por día). Esperá un minuto. |
Listar el catálogo
/v1/itemsEl catálogo del comercio, por páginas. Para recorrerlo entero, pedí la primera página con after vacío y seguí con el valor de next hasta que venga null.
Cada artículo trae más campos que los documentados; usá solo los documentados, los demás pueden cambiar sin aviso.
Parámetros
| Campo | Tipo | Descripción |
|---|---|---|
after | string | Recorrido por páginas: vacío para la primera, después el next de la respuesta anterior. |
limit | integer | Artículos por página (máximo 200). |
q | string | Búsqueda por nombre o SKU. |
Ejemplo de llamado
Primera página
curl "$PUNTO_API/v1/items?after=&limit=200" \
-H "Authorization: Bearer $PUNTO_KEY"Respuesta (dentro de data)
| Campo | Tipo | Descripción |
|---|---|---|
items | array<object> | |
items[].itemId | string (uuid) | |
items[].itemName | string | |
items[].itemSKU | string | null | |
items[].itemPrice | number | Precio de lista del catálogo. |
items[].itemStatus | integer | 1 activo; otro valor, archivado. |
items[].itemCanSale | boolean | Si se puede vender. |
items[].itemTrackInventory | boolean | Si el comercio controla su stock. |
items[].taxId | string (uuid) | null | |
items[].addonGroups | array<any> | null | Grupos de agregados con sus opciones (optionId y recargo). |
limit | integer | |
next | string | null | Desde dónde pedir la próxima página; null si fue la última. |
total | integer | null | Cantidad total de artículos, solo en la primera página. |
Respuesta de ejemplo
200 · Una página del catálogo.
{
"ok": true,
"data": {
"items": [
{
"itemId": "10223f3b-2e3d-4339-8496-9f288d8be65b",
"itemName": "Café en grano 250 g",
"itemSKU": "CAFE-250",
"itemPrice": 45000,
"itemStatus": 1,
"itemCanSale": true,
"itemTrackInventory": true,
"taxId": "3cf780bb-51d6-4b41-b52d-1e77bfb60969",
"addonGroups": null
},
{
"itemId": "61230b1e-90e8-4018-ac59-865ca957b293",
"itemName": "Hamburguesa clásica",
"itemSKU": "HAMB-CLASICA",
"itemPrice": 38000,
"itemStatus": 1,
"itemCanSale": true,
"itemTrackInventory": false,
"taxId": "3cf780bb-51d6-4b41-b52d-1e77bfb60969",
"addonGroups": [
{
"groupId": "c1a2b3c4-d5e6-4f70-8a91-b2c3d4e5f600",
"name": "Extras",
"options": [
{
"optionId": "c1a2b3c4-d5e6-4f70-8a91-b2c3d4e5f604",
"name": "Queso cheddar",
"priceDelta": 4000
}
]
}
]
}
],
"limit": 200,
"next": null,
"total": 2
}
}Códigos de respuesta
| 200 | Una página del catálogo. |
| 401 | Falta la key o no es válida. |
| 403 | La key no puede hacer esto. reason: addon_api_required (el comercio no tiene activa la API de integraciones), api_key_scope_missing (la key no es de integración), account_blocked, account_suspended o account_inactive (la cuenta del comercio no está operativa). Si el usuario que emitió la key no tiene permiso para vender, o si pedís una sucursal (outletId) a la que la key no tiene acceso, responde 403 sin reason. |
| 429 | Pasaste el límite de uso de la key (60 por minuto, 5.000 por día). Esperá un minuto. |
Listar los medios de pago
/v1/payment-methodsLos medios de pago del comercio. En una venta, nombrá el medio con el que cobró tu sistema por su id. El efectivo (cash: true) no se acepta en ventas de esta API.
Ejemplo de llamado
Medios de pago
curl "$PUNTO_API/v1/payment-methods" \
-H "Authorization: Bearer $PUNTO_KEY"Respuesta (dentro de data)
| Campo | Tipo | Descripción |
|---|---|---|
paymentMethods | array<object> | |
paymentMethods[].id | string (uuid) | Lo que mandás en payment.method. |
paymentMethods[].name | string | |
paymentMethods[].code | string | Código corto del medio, si el comercio le asignó uno. |
paymentMethods[].cash | boolean | Si es efectivo. No se acepta en ventas de esta API. |
paymentMethods[].requiresIdentifier | boolean | Si el comercio pide un número de comprobante del cobro (mandalo en payment.reference). |
paymentMethods[].identifierLabel | string | Cómo se llama ese número para el comercio. |
Respuesta de ejemplo
200 · Los medios de pago.
{
"ok": true,
"data": {
"paymentMethods": [
{
"id": "e7a1c3d5-2b49-4f60-8d17-5c9e0a3b6f26",
"name": "Efectivo",
"code": "A",
"cash": true,
"requiresIdentifier": false,
"identifierLabel": ""
},
{
"id": "e7a1c3d5-2b49-4f60-8d17-5c9e0a3b6f24",
"name": "Tarjeta de crédito",
"code": "S",
"cash": false,
"requiresIdentifier": true,
"identifierLabel": "N.º de autorización"
},
{
"id": "e7a1c3d5-2b49-4f60-8d17-5c9e0a3b6f25",
"name": "Transferencia",
"code": "T",
"cash": false,
"requiresIdentifier": false,
"identifierLabel": ""
}
]
}
}Códigos de respuesta
| 200 | Los medios de pago. |
| 401 | Falta la key o no es válida. |
| 403 | La key no puede hacer esto. reason: addon_api_required (el comercio no tiene activa la API de integraciones), api_key_scope_missing (la key no es de integración), account_blocked, account_suspended o account_inactive (la cuenta del comercio no está operativa). Si el usuario que emitió la key no tiene permiso para vender, o si pedís una sucursal (outletId) a la que la key no tiene acceso, responde 403 sin reason. |
| 429 | Pasaste el límite de uso de la key (60 por minuto, 5.000 por día). Esperá un minuto. |
Listar los impuestos
/v1/taxesLos impuestos del comercio, para elegir el de un envío o recargo (charges[].taxId). Los impuestos de los artículos los aplica Punto solo.
Ejemplo de llamado
Impuestos
curl "$PUNTO_API/v1/taxes" \
-H "Authorization: Bearer $PUNTO_KEY"Respuesta (dentro de data)
| Campo | Tipo | Descripción |
|---|---|---|
taxes | array<object> | |
taxes[].id | string (uuid) | Lo que mandás en charges[].taxId. |
taxes[].name | string | |
taxes[].rate | number | null | Tasa en porcentaje. 0 en un exento. |
taxes[].kind | string | null | rate: con tasa; exempt: exento. Valores: rate, exempt. |
taxes[].isDefault | boolean | El que se aplica a un cargo sin taxId. |
Respuesta de ejemplo
200 · Los impuestos, en el orden del comercio.
{
"ok": true,
"data": {
"taxes": [
{
"id": "3cf780bb-51d6-4b41-b52d-1e77bfb60969",
"name": "IVA 10%",
"rate": 10,
"kind": "rate",
"isDefault": true
},
{
"id": "8a4d2c6e-1f37-4b85-9c20-6e3a7d1b5f42",
"name": "IVA 5%",
"rate": 5,
"kind": "rate",
"isDefault": false
},
{
"id": "8a4d2c6e-1f37-4b85-9c20-6e3a7d1b5f43",
"name": "Exento",
"rate": 0,
"kind": "exempt",
"isDefault": false
}
]
}
}Códigos de respuesta
| 200 | Los impuestos, en el orden del comercio. |
| 401 | Falta la key o no es válida. |
| 403 | La key no puede hacer esto. reason: addon_api_required (el comercio no tiene activa la API de integraciones), api_key_scope_missing (la key no es de integración), account_blocked, account_suspended o account_inactive (la cuenta del comercio no está operativa). Si el usuario que emitió la key no tiene permiso para vender, o si pedís una sucursal (outletId) a la que la key no tiene acceso, responde 403 sin reason. |
| 429 | Pasaste el límite de uso de la key (60 por minuto, 5.000 por día). Esperá un minuto. |

