# Nilo Integration API > Nilo is a B2B e-commerce platform connecting suppliers and stores. This API allows suppliers to manage their product catalog, stores, orders, promotions, and more from their ERP systems. ## Authentication POST /login Body: { "clientId": "your-client-id", "clientSecret": "your-client-secret" } Response: { "access_token": "eyJ...", "token_type": "Bearer", "expires_in": 3600 } All subsequent requests require these headers: - Authorization: Bearer {access_token} - x-api-key: {your-api-key} - Content-Type: application/json (for POST/PUT) ## Brands POST /brand — Create a brand Body: { "name": "Brand 1", "internalCode": "B001", "weight": 1, "image": "/brand/img.png" } GET /brand — List all brands (paginated). Query: take, page, enabled, cursor GET /brand/{code} — Get brand by code PUT /brand — Upsert brand (create or update by internalCode) Body: { "name": "Brand 1", "internalCode": "B001", "enabled": true, "weight": 1, "image": "/brand/img.png" } PUT /brand/{code} — Update brand Body: { "name": "Updated Name", "weight": 2, "image": "/brand/new.png" } PUT /brand/{code}/status — Enable/disable brand Body: { "enabled": true } POST /batch/brand — Batch upsert brands (async, returns batchIds) Body: { "upsert": [{ "name": "Brand", "internalCode": "B001", "enabled": true }] } ## Categories POST /category — Create category Body: { "name": "Category 1", "internalCode": "CAT01", "weight": 1, "image": "/cat/img.png" } GET /category — List categories (paginated). Query: take, page, enabled, cursor GET /category/{code} — Get category by code PUT /category — Upsert category Body: { "name": "Category 1", "internalCode": "CAT01", "enabled": true, "weight": 1 } PUT /category/{code} — Update category Body: { "name": "Updated Name", "weight": 2 } PUT /category/{code}/status — Enable/disable category Body: { "enabled": true } ## Subcategories POST /subcategory — Create subcategory Body: { "name": "Sub 1", "internalCode": "SUB01", "parentCategoryInternalCode": "CAT01", "weight": 1 } PUT /subcategory — Upsert subcategory Body: { "name": "Sub 1", "internalCode": "SUB01", "parentCategoryInternalCode": "CAT01", "enabled": true } PUT /subcategory/{code} — Update subcategory Body: { "name": "Updated", "parentCategoryInternalCode": "CAT01" } PUT /subcategory/{code}/status — Enable/disable subcategory Body: { "enabled": true } ## Products POST /product — Create product Body: { "name": "Product 1", "internalCode": "SKU001", "description": "Desc", "brandInternalCode": "B001", "categoryInternalCode": "CAT01", "displayProducts": [{ "default": true, "units": 1, "internalCode": "SKU001.1" }], "mainImage": "https://img.jpg", "images": ["https://img2.jpg"] } GET /product — List products (paginated). Query: take, page, enabled, cursor GET /product/{code} — Get product by code PUT /product — Upsert product Body: { "internalCode": "SKU001", "name": "Product 1", "description": "Desc", "brandInternalCode": "B001", "categoryInternalCode": "CAT01", "enabled": true, "displayProducts": [{ "default": true, "units": 1, "internalCode": "SKU001.1" }] } PUT /product/{code}/status — Change product/display status Body: { "enabled": true, "units": 1 } POST /batch/product — Batch create/update/upsert products (async) Body: { "upsert": [{ "internalCode": "SKU001", "name": "Product", "description": "Desc", "brandInternalCode": "B001", "categoryInternalCode": "CAT01", "enabled": true, "displayProducts": [{ "default": true, "units": 1, "internalCode": "SKU001.1" }] }] } Actions: create, update, upsert (send as separate arrays) ## Stock POST /product/{code}/stock — Update product stock/availability for a grouper Body: { "grouperCode": "WH001", "available": true, "units": 1 } POST /batch/stock/grouper — Batch update stock by grouper (async) Body: { "availability": [{ "productCode": "SKU001", "grouperCode": "WH001", "available": true }] } ## Price Lists POST /pricelist — Create price list Body: { "internalCode": "PL001", "name": "Price List 1" } GET /pricelist/{code} — Get price list by code PUT /pricelist/{code}/status — Enable/disable price list Body: { "enabled": true } POST /batch/pricelist — Batch update price list products and stores Body: { "upsert": { "products": [{ "priceListInternalCode": "PL001", "productInternalCode": "SKU001.1", "units": 3, "price": 700.20 }], "stores": [{ "priceListInternalCode": "PL001", "storeInternalCode": "ST001" }] }, "remove": { "products": [], "stores": [] } } ## Stores POST /store — Create store Body: { "ownerName": "Owner", "internalCode": "ST001", "addresses": [{ "addressLine": "Street 123", "zipCode": "2400" }], "legalName": "Company S.A.", "legalId": "30-12345678-9", "stockGrouperCode": "WH001", "priceListCode": "PL001" } GET /store — List stores (paginated). Query: take, page, cursor, enabled GET /store/{code} — Get store by code PUT /store — Upsert store Body: { "internalCode": "ST001", "ownerName": "Owner", "addresses": [{ "addressLine": "Street 123" }], "legalName": "Company S.A.", "legalId": "30-12345678-9", "enabled": true } PUT /store/{code} — Update store (same fields as upsert) PUT /store/{code}/status — Enable/disable store Body: { "enabled": true } PUT /store/{code}/add/user — Add user to store by phone Body: { "countryCode": "+54", "phone": "1123456789", "name": "John Doe" } PUT /store/{code}/add/user-email — Add user to store by email Body: { "email": "user@example.com", "name": "John Doe" } PUT /store/{code}/remove/user — Remove users from store Body: { "users": [{ "countryCode": "+54", "phone": "1123456789" }] } POST /store/{code}/credit — Update store credit limit Body: { "creditLimit": 5000 } POST /store/{code}/debtor — Mark store as defaulter (blocks orders). No body required. POST /store/{code}/latepayer — Mark store as late payer. No body required. POST /batch/store/setting — Batch update store settings Body: { "stores": [{ "storeCode": "ST001", "settings": { "credit": 5000, "debtor": false, "latePayer": false, "allowPartialDelivery": true, "minimumOrderAmount": 1000 } }] } POST /batch/store — Batch create/update/upsert/remove stores (async) Body: { "upsert": [{ "internalCode": "ST001", "ownerName": "Owner", "addresses": [{ "addressLine": "Street" }], "legalName": "Name", "legalId": "ID", "enabled": true }] } Actions: create, update, upsert, remove (send as separate arrays) ## Promotions GET /promotion/{code} — Get promotion by code GET /promotion — List promotions (paginated). Query: take, page, cursor, enabled, from, to PUT /promotion/discount/simple — Upsert simple discount (single product, tiered) Body: { "name": "Summer Sale", "internalCode": "PROMO01", "start": "2025-06-01", "end": "2025-08-31", "products": { "internalCode": "SKU001", "boxRatio": 1 }, "discountTiersData": [{ "min": 1, "max": 5, "discountPercentage": 10 }], "enabled": true, "promotionGrouperCodes": ["GROUP1"] } Note: use discountTiersData (percentage) OR discountTiersDataCash (fixed amount), mutually exclusive. PUT /promotion/discount/multiple — Upsert multiple-product discount Body: { "name": "Bulk Purchase", "internalCode": "PROMO02", "start": "2025-06-01", "end": "2025-08-31", "products": [{ "internalCode": "SKU001", "boxRatio": 1 }, { "internalCode": "SKU002", "boxRatio": 1 }], "discountTiersData": [{ "min": 1, "max": 5, "discountPercentage": 10 }], "enabled": true } PUT /promotion/bonus/individual — Upsert individual bonus (buy X get Y same product) Body: { "name": "Buy 2 Get 1", "internalCode": "PROMO03", "start": "2025-06-01", "end": "2025-08-31", "products": { "internalCode": "SKU001", "boxRatio": 1 }, "bonusTierData": { "quantityRequired": 2, "quantityGiven": 1, "units": 1 }, "enabled": true } PUT /promotion/bonus/crossed — Upsert crossed bonus (buy product A, get product B) Body: { "name": "Cross Promo", "internalCode": "PROMO04", "start": "2025-06-01", "end": "2025-08-31", "products": [{ "internalCode": "SKU_A", "boxRatio": 1 }], "bonusTierData": { "quantityRequired": 2, "quantityGiven": 1, "productInternalCode": "SKU_B", "units": 1 }, "enabled": true } PUT /promotion/{code}/status — Enable/disable promotion Body: { "enabled": true } POST /batch/promotion — Batch upsert/remove promotions (all types, async) Body: { "upsert": { "discount": { "simple": [...], "multiple": [...] }, "bonus": { "individual": [...], "crossed": [...] } } } POST /batch/promotion/update/grouper — Batch add/remove promotion groupers Body: { "add": { "groups": [{ "internalCode": "PROMO01", "groupInternalCode": "GROUP1" }] }, "remove": { "groups": [...] } } ## Orders GET /order/{id} — Get order by ID GET /order — List orders (paginated). Query: take, page, cursor, status (PENDING/CONFIRMED/DELIVERED/CANCELLED), from, to PUT /order/{id}/confirm — Confirm an order Body: { "deliveryDate": "2025-12-03" } PUT /order/{id}/deliver — Mark order as delivered Body (complete): { "complete": true } Body (partial): { "complete": false, "partialDelivery": { "date": "2025-12-03", "products": [{ "productCode": "SKU001.1", "units": 1, "quantity": 5 }] } } PUT /order/{id}/return — Return products from delivered order Body: { "date": "2025-12-03", "products": [{ "productCode": "SKU001.1", "units": 1, "quantity": 2 }] } PUT /order/{id}/cancel — Cancel an order Body: { "code": "OUT_OF_STOCK" } Codes: IMPRECISE_DELIVERY_ADDRESS, OUT_DELIVERY_HOURS, OUT_TIME_CONFIRM, OUTSTANDING_BALANCE, OUT_OF_STOCK, OTHER POST /batch/order — Batch order status changes (async) Body: { "status": { "confirm": [{ "id": 10, "deliveryDate": "2025-12-03" }], "deliver": [{ "id": 5, "complete": true }], "cancel": [{ "id": 7, "code": "OUT_OF_STOCK" }] } } POST /order/bulk-import — Import historical orders Body: { "orders": [{ "id": 10, "source": "ERP", "buyer": { "storeCode": "ST001", "routeCode": "R001" }, "address": { "addressLine": "Street", "zipCode": "2400" }, "status": "DELIVERED", "createdAt": "2025-12-03", "products": [{ "productCode": "SKU001.1", "units": 1, "quantity": 5 }] }] } ## Loyalty Challenges GET /loyaltychallenge — List challenges (paginated). Query: take, page, cursor, enabled, from, to GET /loyaltychallenge/{code} — Get challenge by code PUT /loyaltychallenge — Upsert loyalty challenge Body: { "title": "Summer Points", "internalCode": "CH001", "pointsGiven": 100, "requiredAmount": 500, "start": "2025-06-01", "end": "2025-08-31", "enabled": true, "products": [{ "internalCode": "SKU001" }], "description": "Earn points", "image": "https://img.jpg" } PUT /loyaltychallenge/{code}/status — Enable/disable challenge Body: { "enabled": true } ## Recommendations GET /recommendations — List recommendations (paginated). Query: take, page, cursor, enabled GET /recommendations/{code} — Get recommendation by code PUT /recommendations — Upsert recommendation Body: { "title": "Summer Picks", "internalCode": "REC001", "description": "Best for summer", "startDate": "2025-06-01", "endDate": "2025-08-31", "weight": 1, "products": [{ "internalCode": "SKU001" }] } PUT /recommendations/{code}/status — Enable/disable recommendation Body: { "enabled": true } POST /batch/recommendations — Batch add/remove stores from recommendations Body: { "add": { "stores": [{ "recommendationInternalCode": "REC001", "storeInternalCode": "ST001" }] }, "remove": { "stores": [...] } } ## Stock Groupers POST /grouper/stock — Create stock grouper (warehouse) Body: { "name": "Warehouse North", "internalCode": "WH001" } PUT /grouper/{code}/stock — Update stock grouper name Body: { "name": "Updated Name" } PUT /grouper/{code}/status/stock — Enable/disable stock grouper Body: { "enabled": true } POST /batch/grouper/stock — Batch add/remove stores from stock groupers Body: { "add": { "stores": [{ "groupInternalCode": "WH001", "storeInternalCode": "ST001" }] }, "remove": { "stores": [...] } } ## Promotion Groupers POST /grouper/promotion — Create promotion grouper Body: { "name": "Promo Group 1", "internalCode": "PG001" } PUT /grouper/{code}/promotion — Update promotion grouper name Body: { "name": "Updated Name" } PUT /grouper/{code}/status/promotion — Enable/disable promotion grouper Body: { "enabled": true } POST /batch/grouper/promotion — Batch add/remove stores from promotion groupers Body: { "add": { "stores": [{ "groupInternalCode": "PG001", "storeInternalCode": "ST001" }] }, "remove": { "stores": [...] } } ## Commercial Routes POST /batch/commercial-route — Batch upsert commercial routes (async) Body: { "upsert": [{ "internalCode": "RUTA-001", "name": "Route North", "enabled": true }, { "internalCode": "RUTA-002", "name": "Route South", "enabled": true }] } Response 202: { "code": 202, "batchIds": { "upsert": ["uuid"] }, "message": "Operation in processing" } ## Supplier GET /supplier/cancelcodes — Get allowed cancellation reason codes. No body. Response: [{ "reason": "I will place a new order", "code": "NEW_ORDER", "usage": "STORE" }] ## Support Tickets GET /support/ticket — List tickets (paginated). Query: take, page, status (CLOSED/PENDING/IN_PROGRESS/ADDITIONAL_INFO_REQUESTED/SOLVED) GET /support/ticket/{id} — Get ticket by ID PUT /support/ticket/close — Close a ticket Body: { "message": "Issue resolved.", "ticketId": 1234 } Note: use either ticketId (number) or externalTicketId (string). POST /support/ticket/additional-info — Request additional info from store Body: { "message": "Please provide order number.", "ticketId": 1234 } ## Batch Process Tracking All batch endpoints return batchIds (UUIDs). Use these to track progress: GET /batch/{uuid}/status — Get batch summary Response: { "uuid": "...", "status": "processing|completed|failed", "summary": { "total": 100, "processed": 50, "failed": 10, "completed": 40 } } GET /batch/{uuid}/detail — Get per-item results Response: { "uuid": "...", "detail": [{ "code": "...", "endpoint": "...", "action": "...", "status": "completed|failed", "message": "...", "finishedAt": "...", "createdAt": "..." }] } ## Webhooks (events Nilo sends to YOUR system) You implement these endpoints. Nilo calls them when events occur. ### Order Created (POST to your webhook URL) Payload: { "id": 10, "buyer": { "storeCode": "ST001", "routeCode": "R001" }, "address": { "internalCode": "1234", "addressLine": "Street 123", "zipCode": "2400" }, "products": [{ "code": "SKU001.1", "units": 2, "quantity": 10, "unitFinalPrice": { "value": 0.9, "text": "USD 0.9" }, "unitPrice": { "value": 1, "text": "USD 1" }, "subtotal": { "value": 10, "text": "USD 10" }, "total": { "value": 9, "text": "USD 9" } }], "promotions": { "rewards": [{ "promoInternalCode": "PROMO01", "quantity": 1, "products": [{ "internalCode": "SKU002", "units": 1 }] }], "discounts": [{ "promoInternalCode": "PROMO02", "appliedDiscountPercentage": 10, "appliedDiscountAmount": 0.1, "products": [{ "internalCode": "SKU001.1", "units": 2 }] }] }, "status": "PENDING", "createdAt": "2025-12-03T10:15:30Z" } ### Order Cancelled (PUT to your webhook URL) Payload: { "id": 10, "status": "CANCELLED", "reason": "I will place a new order", "code": "NEW_ORDER", "buyer": { "storeCode": "ST001" } } ### Store User Created/Deleted (POST to your webhook URL) Payload: { "subject": "user-created", "store": { "id": 1, "internalCode": "ST001" }, "user": { "username": "+5412345678", "name": "Seller Name" } } Note: subject is "user-created" or "user-deleted". ### Support Ticket Created (POST to your webhook URL) Payload: { "ticket_id": "1122", "client_internal_code": "ST001", "client_name": "Store Name", "phone_number": "+5412345678", "email": "seller@store.com", "description": "Support request text", "issue_type": "Account support" } ### Support Ticket Additional Info Received (POST to your webhook URL) Payload: { "ticket_id": "1122", "external_ticket_id": "EXT-5678", "client_internal_code": "ST001", "client_email": "seller@store.com", "response": "Here is the additional information" } ## Common Integration Patterns ### Full Catalog Sync (recommended for catalogs < 10k products) 1. Authenticate via POST /login 2. Use POST /batch/product with action "upsert" to send all products 3. Nilo handles create vs update automatically (idempotent) 4. Track progress via GET /batch/{uuid}/status ### Recommended Setup Order 1. Create stock groupers (warehouses): POST /grouper/stock 2. Create promotion groupers: POST /grouper/promotion 3. Create brands: POST /batch/brand 4. Create categories and subcategories 5. Create products: POST /batch/product 6. Set stock/availability: POST /batch/stock/grouper 7. Create price lists and assign products/stores: POST /batch/pricelist 8. Create stores: POST /batch/store 9. Set up promotions: POST /batch/promotion 10. Configure webhooks to receive orders ### Receiving Orders 1. Configure your webhook URL in Nilo 2. Nilo sends POST with order data to your URL when a new order is created 3. Process the order in your ERP 4. Confirm via PUT /order/{id}/confirm 5. When shipped, mark as delivered via PUT /order/{id}/deliver ### Pagination All list endpoints support cursor-based pagination: - First request: GET /resource?take=50 - Next page: GET /resource?take=50&cursor={lastCursor} - Response includes page.cursorBased.hasNextPage and page.cursorBased.lastCursor