Skip to main content
Un pedido por API es un pedido tuyo como cualquier otro: lo ves en tu panel, lo pagás desde ahí y Gudink lo produce y lo envía a tu comprador. Los ejemplos usan dos variables de entorno. Así como están apuntan al entorno de pruebas; para producción cambiá las dos (https://app.gudink.com/api/v1 y tu clave gk_live_):

Crear un pedido

Este ejemplo es un envío a domicilio. Para un retiro, ver Retiro en sucursal.
Respuesta 201 (la misma forma que devuelve GET /orders/:id):

Retiro en sucursal

Si tu comprador elige la opción de la cotización con type: "pickup", el pedido no lleva dirección: se entrega en la sucursal que eligió. Cambian dos cosas del cuerpo:
  • shipping lleva, además de quote_id y service_code (el de la opción de retiro), pickup_point_id: el id de la sucursal elegida, de pickup_points de esa opción y esa cotización.
  • shipping_address no va. Mandar las dos cosas (o ninguna) responde 400 validation_error.
recipient sigue siendo obligatorio: es quien retira, y su nombre y su teléfono van a la etiqueta. Todo lo demás (precio, external_id, vencimiento, pago, estados, avisos) funciona igual que a domicilio. El flujo completo, listo para copiar (usa jq):
El cuerpo que manda el paso 3, con los valores de la cotización de ejemplo de Cotizar el envío:
En la respuesta (y en GET /orders/:id y en los avisos), un retiro sale así:
  • Cuando el pedido sale (shipped), el seguimiento funciona igual que a domicilio. Avisale a tu comprador que lo retire en esa sucursal, con su documento: Gudink no le escribe.
  • Errores propios del retiro, además de los de siempre:
    • 400 pickup_point_required: la opción elegida es un retiro y mandaste shipping_address en lugar de shipping.pickup_point_id.
    • 409 pickup_point_mismatch: el pickup_point_id no es una sucursal de esa opción en esa cotización, o la opción elegida es a domicilio y mandaste una sucursal.
  • En un retiro, quote_mismatch compara las líneas y que el service_code esté en esa cotización: no hay código postal ni provincia del pedido que comparar.
  • En qué orden se deciden: ver Por qué se rechaza un pedido.

Qué va en cada campo

Validá estos formatos en tu checkout antes de cobrarle a tu comprador. El pedido se crea cuando tu comprador ya pagó, y un campo inválido vuelve como 400 validation_error. Reglas que evitan un rechazo después de cobrar:
  • Teléfono: 11 5555-5555 int 4 no pasa, porque tiene letras. Mandá 11 5555-5555.
  • Provincia: si la API reconoce el código postal, la provincia sale del código postal y la que mandes se ignora, no se rechaza. Si no lo reconoce y no la mandás, responde 400 province_required. Mandando siempre la province de la cotización no tenés que distinguir los dos casos.
  • Código postal: 1043 y C1043AAZ cotizan igual (cuentan los 4 dígitos), así que podés cotizar con uno y crear el pedido con el otro. Pero para reintentar el mismo pedido tenés que mandar el mismo texto: ver No duplicar pedidos.
  • No mandes nombre, talle, color ni precio del producto: los pone Gudink a partir del producto y la variante.
  • Cualquier campo que no esté en esta tabla responde 400 validation_error (“Campo desconocido”).
  • No hay cupones ni descuentos por API.
El operador logístico usa el nombre y el teléfono para coordinar la entrega. Gudink no le escribe a tu comprador.

Si tu plataforma guarda la dirección en una sola línea

street y number van separados, y separados se entregan mejor: la altura se busca en su propio campo. Si tu checkout guarda “Av. Corrientes 1234” en un solo campo, lo mejor es pedirle la altura a tu comprador en un campo aparte. Si no podés, separala así (la altura es el último número de la línea; sin número al final, S/N):
  • Revisá las calles que tienen número en el nombre (Calle 50, Ruta 3) y las líneas que traen también piso y departamento (Belgrano 1234 3B): la función no puede saber qué número es la altura. Mostrale a tu comprador cómo quedó antes de cobrarle.
  • La API acepta la línea entera en street con number: "S/N", pero el operador logístico tiene que adivinar la altura: usalo sólo como último recurso.
  • Piso y departamento: si tu plataforma tiene un solo campo libre (“Piso 3, depto B”), mandalo en apartment si entra en 20 caracteres; si es más largo, mandalo en notes (hasta 300). No lo cortes: un texto cortado confunde más que uno en las notas.

Qué compara la cotización con el pedido

Si la cotización no coincide con el pedido, el alta responde 409 quote_mismatch. Se comparan cuatro cosas: Con un código postal que la API reconoce, la provincia que mandes se ignora, no se rechaza: el pedido sale con la del código postal. Por ejemplo, si mandás Salta con el código postal 1425, el pedido queda en Capital Federal, sin error. Sólo cuando tuviste que mandarla vos (un código postal que la API no reconoce) una provincia distinta de la cotizada responde 409 quote_mismatch. En un retiro en sucursal no hay dirección: se comparan sólo las líneas y el service_code.

No duplicar pedidos

Las conexiones se cortan. Si mandaste un pedido y no recibiste respuesta, no sabés si se creó. Para eso está external_id.

Un id único por instalación

  • Es único en toda tu cuenta de Gudink y para siempre: un pedido cancelado lo sigue ocupando.
  • No uses sólo el número de pedido de tu tienda. Ese número se repite si reinstalás la tienda, si tenés una copia de pruebas de tu sitio o si conectás más de una tienda a la misma cuenta de Gudink. Armalo con un prefijo propio de cada instalación más tu número de pedido, por ejemplo woo-k7p2-10432: k7p2 es un código que generás una sola vez al instalar tu integración y guardás en su configuración.
  • Qué pasa si choca (el mismo external_id para dos compras distintas): casi siempre 409 external_id_conflict, con el pedido viejo en details.order_id. No se arregla reintentando: es tu external_id que no es único. Si el cuerpo fuera idéntico (mismas líneas, mismo destinatario, misma dirección y la misma cotización), recibirías 200 con el pedido viejo como si fuera el tuyo. Por eso el prefijo por instalación no es opcional.

Reintentar sin duplicar

  • Reintentá siempre con el mismo external_id y el mismo cuerpo. Si el pedido ya existía, la API responde 200 con ese pedido y no crea otro.
  • El 200 devuelve el pedido como está hoy, que puede estar cancelled. Ver Qué mirar después de cada 200 o 201.
  • Nunca generes un external_id nuevo para reintentar. Crearías un segundo pedido, que también se te cobra.
  • Si reintentás en el primer minuto, mientras el alta original todavía se está completando, puede responder 409 order_in_progress con la cabecera Retry-After (2 segundos). Esperá eso y repetí la misma llamada. Puede terminar en 200 (el pedido quedó) o en un alta nueva, si el original se anuló por falta de stock. Es raro: dos altas iguales casi simultáneas suelen dar 201 y 200. Que nunca lo veas no es un error.
  • El mismo external_id con otro cuerpo (otras líneas, otro destinatario, otra dirección u otro envío) responde 409 external_id_conflict. El pedido que ya existía está en details.order_id.
  • Un intento rechazado no crea nada ni reserva el external_id. Cualquier error del alta (por ejemplo quote_expired, quote_mismatch, out_of_stock o validation_error) deja el external_id libre: podés volver a mandarlo con otro contenido, por ejemplo con una cotización nueva. Lo único que lo ocupa es un pedido creado (201).
  • Un pedido cancelado sigue ocupando su external_id. Volver a pedir lo mismo es un pedido nuevo, con un external_id nuevo.
  • Antes de mandar un cuerpo distinto con un external_id que ya mandaste (por ejemplo, recotizaste porque la cotización venció mientras reintentabas un timeout), consultá GET /orders?external_id=<tu id>. Si data trae un pedido, el intento que creías perdido sí se creó: usá ése. Si viene vacío, el external_id está libre y podés mandar el cuerpo nuevo.

Qué mirar después de cada 200 o 201

Un 2xx de POST /orders quiere decir “éste es el pedido con ese external_id”, no “el pedido sigue en pie”. Si lo cancelaste (o venció) y reenviás el mismo cuerpo, recibís 200 con ese pedido cancelado: no se crea otro. Después de cada 200 o 201, tu tienda tiene que mirar status:
  1. status es cancelled: ese pedido no se va a fabricar. No le confirmes nada a tu comprador con él. Para volver a pedirlo, creá un pedido nuevo con un external_id nuevo (por ejemplo woo-k7p2-10432-2).
  2. Cualquier otro status: el pedido existe y sigue su curso. Ver Estados.

Cómo se compara “el mismo cuerpo”

La comparación es normalizada, no byte a byte: Guardá el cuerpo que mandaste y reenviá exactamente ése. No lo vuelvas a armar desde el pedido de tu plataforma, que puede haber normalizado el código postal, la provincia o el teléfono entre el checkout y el alta. Qué hacer cuando te quedaste sin reintentos y no sabés si el pedido se creó: ver Reintentos.

Qué te cobra Gudink

amounts.subtotal (la suma de items[].total_price) más amounts.shipping (el precio de la opción de envío que elegiste) es amounts.total. Todo en pesos enteros con IVA incluido. La factura sale a nombre de tu cuenta, como cualquier pedido tuyo. items[].unit_price es el precio del momento en que creaste el pedido: es lo que se te cobra aunque el producto cambie de precio después.

El pago y el vencimiento

  • El pedido nace con status: "pending_payment" y payment.status: "pending". Te mandamos un mail con el link de pago.
  • payment.pay_url es el pedido en tu panel de Gudink. Ahí lo pagás con transferencia o Mercado Pago, y podés pagar varios juntos. No es un link para tu comprador.
  • Cuando se acredita el pago, el pedido pasa a paid y entra a producción.
  • Un pedido sin pagar se cancela solo a las 72 horas de creado. expires_at dice exactamente cuándo. El mail con el link de pago y el recordatorio también dicen la fecha y la hora.
  • expires_at es null una vez pagado o cancelado, y mientras dura una revisión de Gudink. Ver Transiciones de estado.
  • Si hay un pago tuyo en curso justo en el momento del vencimiento, Gudink espera a que se resuelva antes de cancelar.
  • Al cancelarse por vencimiento recibís el aviso order.cancelled con cancel_reason: "unpaid_expired".
  • Podés tener hasta 20 pedidos por API sin pagar a la vez. Con 20, el siguiente responde 409 too_many_unpaid_orders: pagá o cancelá alguno.
  • En el entorno de pruebas no pagues desde pay_url: usá la simulación.
Gudink no le cobra a tu comprador ni le escribe. Lo que tu comprador te pagó a vos es independiente de este pago.

Pagar es una tarea de todos los días

Tu comprador ya te pagó a vos, pero Gudink fabrica recién cuando vos pagás el pedido. Si no lo pagás antes de expires_at, se cancela y tu comprador se queda sin su producto aunque te haya pagado. La API no paga pedidos: se pagan en tu panel, y podés pagar varios juntos. Armá tu propia alarma antes del vencimiento, por ejemplo una vez por hora:
  1. Pedí GET /orders?status=pending_payment y seguí con next_cursor hasta que sea null.
  2. Para cada pedido con expires_at en las próximas 24 horas, avisate a vos (un mail, un mensaje interno) con payment.pay_url.
  3. Si igual vence, vas a ver status: "cancelled" con cancel_reason: "unpaid_expired": decidí vos si lo volvés a pedir (con un external_id nuevo) o le devolvés la plata a tu comprador.
No hay un aviso (webhook) de “está por vencer”: expires_at ya viene en cada pedido y en GET /orders, y no cambia (salvo que una revisión de Gudink lo frene). Programá la alarma con el cron del servidor, no con tareas que dependan de las visitas a tu sitio. payment.status es informativo. Lo que manda es status:

Por qué se rechaza un pedido

Las validaciones corren en este orden. Responde la primera que falla: Ningún rechazo de los pasos 1 y 3 a 6 crea un pedido ni ocupa el external_id. Los rechazos del paso 5 se deciden en el orden de la tabla (5.1 a 5.6): si hay más de un problema, responde el primero. Sobre out_of_stock:
  • No queda ningún pedido en la API. Si el stock se agotó en el último instante, en tu panel puede verse uno cancelado, que la API no lista y que no se te cobra.
  • Si lo recibís después de haberle cobrado a tu comprador, tu sistema tiene que resolverlo: ofrecer otra variante, esperar la reposición o devolver el dinero. Para reducir el riesgo, leé GET /products/:id justo antes de cobrar.

Stock y reservas

  • available (en GET /products) es una foto: dice que en ese momento hay al menos una unidad para pedir. No dice cuántas, y puede cambiar un segundo después.
  • Crear un pedido reserva sus unidades hasta que se paga, se cancela o vence (72 horas sin pagar). Mientras tanto esas unidades no están disponibles para otro pedido, tampoco para los tuyos.
  • Por eso un pedido puede responder 409 out_of_stock aunque la variante diga available: true: por ejemplo, si pedís más unidades de las que hay, o si tus propios pedidos sin pagar ya reservaron lo que quedaba.
  • Cancelar un pedido sin pagar libera sus unidades en el momento (POST /orders/{id}/cancel). Cancelá los pedidos que no vas a pagar, en especial los de prueba: si los dejás vencer, retienen stock hasta 72 horas.
Qué hacer con un out_of_stock:
  1. Mirá details.items: dice qué líneas no alcanzan.
  2. No reintentes en bucle. Avisale a tu comprador y elegí: esperar y reintentar más tarde el mismo cuerpo, o cambiar el carrito (por ejemplo, menos unidades).
  3. Si cambiás el carrito, cotizá de nuevo con las líneas nuevas. El external_id sigue libre, porque un rechazo no lo ocupa.
  4. Releé GET /products/:id antes de volver a ofrecer la variante.
Además puede responder los errores de toda ruta con clave (401, 403, 429, 500, 503), los del cuerpo (400 invalid_body, 413 payload_too_large) y 409 conflict (conflicto momentáneo: reintentá con el mismo external_id). Qué trae details en cada uno: ver Errores y límites.

Consultar pedidos

Las rutas de lectura (GET /me, /products, /products/:id, /orders, /orders/:id) sólo pueden responder: 400 validation_error (un parámetro de la consulta mal formado o desconocido), 401, 403, 404 not_found, 429 y 5xx. Un pedido por su id:
Responde el pedido, igual que el 201 del alta, con el estado, los importes, el pago y el seguimiento al día. Un id que no existe o no es tuyo responde 404 not_found. Por tu external_id:
Cada elemento de data es el pedido completo. En el ejemplo se recortó. Si no hay ningún pedido con ese external_id, data viene vacío.

Listar y filtrar

GET /orders lista sólo los pedidos creados por la API, del más nuevo al más viejo.
  • Con el filtro status, una página puede traer menos resultados que limit aunque haya más: seguí con next_cursor hasta que sea null.
  • Los pedidos de Tiendanube y los que cargaste a mano en tu panel no aparecen por la API.

updated_at

updated_at cambia con cada cambio de estado, de pago o de seguimiento. Sirve para dos cosas:
  • Ordenar una consulta contra un aviso que llegó tarde: comparalo con el created_at del aviso para saber cuál es más nuevo.
  • Ponerte al día: pedí GET /orders?updated_after=<la última vez que te pusiste al día> y actualizá los pedidos cuyo updated_at sea más nuevo que el que tenés guardado.

Campos que pueden ser null

El seguimiento

tracking se completa cuando el pedido se despacha: Con algunos operadores el número llega un rato después del despacho: te enterás con el aviso order.tracking_updated. Pasarle el seguimiento a tu comprador es tarea tuya.

Estados

status tiene un solo valor, de esta tabla. Si se cumple más de una condición, gana la primera fila: Motivos de hold_reason (sólo cuando status es on_hold; si no, null): Motivos de cancel_reason (sólo cuando status es cancelled; si no, null): Si aparece un valor de status o de hold_reason que tu código no conoce, tratalo como “en curso”, y uno de cancel_reason que no conocés, como null, hasta actualizar tu código. Ver Versiones.

Transiciones de estado

El camino normal:

Un pedido en revisión antes de pagarlo

Gudink puede revisar un pedido antes de que lo pagues: pending_payment -> on_hold (con hold_reason: "under_review") -> pending_payment.
  • Si al terminar la revisión ese vencimiento ya pasó, el pedido se cancela dentro de la hora siguiente.
  • Llega el aviso order.on_hold cuando entra en revisión. La vuelta a pending_payment no tiene aviso propio: consultá el pedido.
  • Mostrá “pagá este pedido” sólo cuando status es pending_payment.

Cancelar

  • La llamada va sin cuerpo.
  • Por API sólo se cancela un pedido que todavía no pagaste. Cancelar un pedido pago implica un reembolso, y eso se hace desde tu panel, no con una clave. Ver Cancelación de pedidos.
  • Límite: 30 cancelaciones por minuto por cuenta.