Skip to main content
All endpoints require JWT authentication via Authorization: Bearer <token> header.

Get All Orders

GET /api/admin/orders

Fetch recent orders with product details and shipping information

Authentication

Required. Token validated via verifyAdminToken(req) helper.

Response

items
array
Array of order objects (limited to 100 most recent)

Implementation

From orders.js:16-24:
const orders = await prisma.order.findMany({
  orderBy: { createdAt: 'desc' },
  include: {
    items: {
      include: { product: true } // Include product details
    }
  },
  take: 100 // Limit for now
});

Example Response

{
  "items": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "pin": "1234",
      "customer_name": "María García",
      "created_at": "2026-03-04T10:30:00Z",
      "status": "CONFIRMED",
      "total": 89900,
      "payment_status": "PAID",
      "payment_method": "WOMPI",
      "shipping_info": {
        "address": "Calle 123 #45-67",
        "city": "Bogotá",
        "phone": "3001234567"
      },
      "line_items": [
        {
          "name": "Jabón Natural de Lavanda",
          "quantity": 2,
          "sku": "KAIU-LAV-100"
        }
      ],
      "shipments": []
    }
  ]
}

Generate Shipping Label

POST /api/admin/generate-label

Create shipment and generate shipping labels via Venndelo API

Request Body

orderIds
array
required
Array of KAIU internal order UUIDs (not Venndelo IDs)

Response

status
string
SUCCESS when label is ready, PROCESSING if still generating
data
string
PDF URL for the shipping label (when status is SUCCESS)

Process Flow

  1. Resolve Venndelo IDs: Fetch externalId from database for each order
  2. Create Shipment (if needed): Call Venndelo /shipping/create-shipments
  3. Poll for Label: Retry up to 20 times (30 seconds max) waiting for label generation
  4. Send Email: Automatically send shipping confirmation with tracking number
From generate-label.js:92-113:
let attempts = 0;
const maxAttempts = 20;

while (attempts < maxAttempts) {
  attempts++;
  
  const payload = {
    order_ids: venndeloIds,
    format: "LABEL_10x15",
    output: "URL"
  };

  const response = await fetch(
    `https://api.venndelo.com/v1/admin/shipping/generate-labels`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Venndelo-Api-Key': VENNDELO_API_KEY
      },
      body: JSON.stringify(payload)
    }
  );
  // ... check status and retry if PROCESSING
}

Example Request

const response = await fetch('https://api.kaiucol.com/api/admin/generate-label', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
  },
  body: JSON.stringify({
    orderIds: ['550e8400-e29b-41d4-a716-446655440000']
  })
});

Example Response

{
  "status": "SUCCESS",
  "data": "https://venndelo-labels.s3.amazonaws.com/label-12345.pdf"
}
Common Errors:
  • Insufficient balance: Venndelo prepaid account needs recharge
  • Shipment creation fails if order already has active shipment
  • Label generation requires valid shipping address and product dimensions

Request Pickup

POST /api/admin/request-pickup

Request carrier pickup for ready-to-ship orders

Request Body

orderIds
array
required
Array of KAIU internal order UUIDs

Behavior

  1. Resolves Venndelo external IDs from database
  2. Calls Venndelo /shipping/request-pickup
  3. Auto-updates order status to PICKUP_REQUESTED
From request-pickup.js:68-77:
// AUTO-UPDATE LOCAL STATUS TO SHIPPED
await prisma.order.updateMany({
  where: { 
    externalId: { in: venndeloIds } 
  },
  data: { 
    status: 'PICKUP_REQUESTED', 
    updatedAt: new Date() 
  }
});

Example Request

await fetch('https://api.kaiucol.com/api/admin/request-pickup', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer <token>',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    orderIds: ['550e8400-e29b-41d4-a716-446655440000']
  })
});

Sync Shipments

POST /api/admin/sync-shipments

Synchronize tracking status for active orders from logistics providers

Authentication

Required JWT token.

Process

  1. Fetch Active Orders: Orders with status PENDING through SHIPPED that have externalId
  2. Query Logistics: Calls LogisticsManager.getShipmentStatus() for each order
  3. Update Status: Updates database if status changed
  4. Batch Limit: Processes 50 orders per request (ordered by oldest updatedAt)
From sync-shipments.js:20-38:
const activeOrders = await prisma.order.findMany({
  where: {
    externalId: { not: null },
    status: {
      in: ['PENDING', 'CONFIRMED', 'PROCESSING', 'READY_TO_SHIP', 
           'PICKUP_REQUESTED', 'SHIPPED']
    }
  },
  take: 50, // Límite por lote para evitar timeouts
  orderBy: { updatedAt: 'asc' }, // Priorizar las que no se han actualizado recientemente
  select: { 
    id: true, 
    externalId: true, 
    status: true, 
    carrier: true, 
    readableId: true, 
    trackingNumber: true,
    paymentMethod: true
  }
});

Response

success
boolean
True if sync completed
processed
number
Number of orders checked
updated
number
Number of orders with status changes
details
array
Array of updates

Example Response

{
  "success": true,
  "processed": 50,
  "updated": 3,
  "details": [
    {
      "id": "1234",
      "old": "PICKUP_REQUESTED",
      "new": "SHIPPED"
    },
    {
      "id": "1235",
      "old": "SHIPPED",
      "new": "DELIVERED"
    }
  ]
}
Run this endpoint periodically (e.g., every 30 minutes) via a cron job to keep order statuses up-to-date.

Build docs developers (and LLMs) love