POST /auth/otp/verify, then send Authorization: Bearer <access-token>. Localhost OTP is always 1234. Lists use ?limit=10&offset=0 (max 100). Errors: {"error":"..."} with 401/403/422/429 codes. Supply status: 1 In review, 2 Approved, 3 Change Request, 4 Rejected.System
/healthLiveness check.
Request
curl -X GET 'http://localhost:3000/health'
Response #1
{
"ok": true,
"service": "ammikids-ops",
"time": "2026-09-23T17:48:00.780Z"
}
/docs/openapi.jsonMachine-readable OpenAPI 3.0 spec generated from this catalog.
Request
curl -X GET 'http://localhost:3000/docs/openapi.json'
Response #2
{
"openapi": "3.0.0",
"info": {
"title": "AmmiKids Ops API"
}
}
Auth
/auth/signupPublic self signup, non-admin types only. Starts UNAPPROVED: login works, dashboards hidden until admin approves. Auto-creates a PENDING supplier/karigar master for those types.
Request
curl -X POST 'http://localhost:3000/auth/signup' \
-H 'Content-Type: application/json' \
-d '{"phone":"9876543210","roleCode":"supplier","displayName":"Sharma Fabrics"}'
Body
{
"phone": "9876543210",
"roleCode": "supplier",
"displayName": "Sharma Fabrics"
}
Response #3
{
"ok": true,
"userId": 9,
"message": "Registered. Login with OTP; admin approval is required for dashboard access."
}
/auth/signup/rolesPublic account-type picker for the signup page.
Request
curl -X GET 'http://localhost:3000/auth/signup/roles'
Response #4
{
"ok": true,
"roles": [
{
"code": "supplier",
"name": "Supplier"
}
]
}
/auth/bootstrapCreate the very first Admin (one-time). Fails 409 once any user exists.
Request
curl -X POST 'http://localhost:3000/auth/bootstrap' \
-H 'Content-Type: application/json' \
-d '{"phone":"+910000000000","setupToken":"change-me-one-time-token","displayName":"Owner"}'
Body
{
"phone": "+910000000000",
"setupToken": "change-me-one-time-token",
"displayName": "Owner"
}
Response #5
{
"ok": true,
"userId": 1
}
/auth/otp/requestSend WhatsApp OTP. Always returns the generic message (no account enumeration). Localhost/dev OTP is always 1234.
Request
curl -X POST 'http://localhost:3000/auth/otp/request' \
-H 'Content-Type: application/json' \
-d '{"phone":"9876543210"}'
Body
{
"phone": "9876543210"
}
Response #6
{
"ok": true,
"message": "If registered and active, an OTP has been sent."
}
/auth/otp/verifyVerify OTP → access (90d) + rotating refresh (90d, sliding). Tokens stored in app localStorage with silent refresh. Supplier first login includes isFirstLogin + profileNotice. Phones in config/superadmins.txt auto-provision as Admin, no activation needed.
Request
curl -X POST 'http://localhost:3000/auth/otp/verify' \
-H 'Content-Type: application/json' \
-d '{"phone":"9876543210","otp":"1234"}'
Body
{
"phone": "9876543210",
"otp": "1234"
}
Response #7
{
"ok": true,
"access": "eyJhbGciOi...",
"refresh": "9f2c...",
"isFirstLogin": true,
"profileNotice": "your profile is under verification, please contact Ammikids",
"user": {
"id": 7,
"phone": "+919876543210",
"display": "Sharma Fabrics",
"role": "supplier"
}
}
/auth/refreshRotate refresh token → new access + refresh pair.
Request
curl -X POST 'http://localhost:3000/auth/refresh' \
-H 'Content-Type: application/json' \
-d '{"refreshToken":"9f2c..."}'
Body
{
"refreshToken": "9f2c..."
}
Response #8
{
"ok": true,
"access": "eyJhbGciOi...",
"refresh": "b71d..."
}
/auth/logoutRevoke this refresh token, or everything with {all:true} (bumps token_version).
Request
curl -X POST 'http://localhost:3000/auth/logout' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"all":true}'
Body
{
"all": true
}
Response #9
{
"ok": true
}
/auth/meCurrent user + server-resolved dashboard. Pending suppliers also get profileNotice.
Request
curl -X GET 'http://localhost:3000/auth/me' \ -H 'Authorization: Bearer <access-token>'
Response #10
{
"ok": true,
"user": {
"id": 7,
"phone": "+919876543210",
"display": "Sharma Fabrics",
"role": "supplier",
"perms": [
"fabrics.submit"
],
"dashboard": "Supplier Dashboard"
}
}
Users
/usersInvite a user (Admin). Optionally link supplier/karigar master.
Request
curl -X POST 'http://localhost:3000/users' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"phone":"9811111111","roleCode":"supplier","supplierId":3}'
Body
{
"phone": "9811111111",
"roleCode": "supplier",
"supplierId": 3
}
Response #11
{
"ok": true,
"userId": 8
}
/users?limit=10&offset=0Paginated user list. Phones masked unless users.view_pii. Filters: ?role=<code>&active=1|0&q=<phone/name>.
Request
curl -X GET 'http://localhost:3000/users?limit=10&offset=0' \ -H 'Authorization: Bearer <access-token>'
Response #12
{
"ok": true,
"items": [
{
"id": 8,
"phone_e164": "XXXXXX1111",
"display_name": "9811111111",
"role": "supplier",
"is_active": 1,
"is_verified": 0,
"last_login_at": null,
"created_at": "2026-09-23T10:00:00.000Z"
}
],
"limit": 10,
"offset": 0,
"total": 42,
"users": [
"...same as items..."
]
}
/users/rolesAll roles with their permission codes (drives admin create/role-change forms).
Request
curl -X GET 'http://localhost:3000/users/roles' \ -H 'Authorization: Bearer <access-token>'
Response #13
{
"ok": true,
"roles": [
{
"id": 2,
"code": "supplier",
"name": "Supplier",
"is_active": 1,
"permissions": [
"fabrics.submit"
]
}
]
}
/users/:id/deactivateInstant block: sessions revoked via token_version.
Request
curl -X PATCH 'http://localhost:3000/users/:id/deactivate' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"reason":"Left the company"}'
Body
{
"reason": "Left the company"
}
Response #14
{
"ok": true
}
/users/:id/reactivateRe-activate. User must OTP-login again.
Request
curl -X PATCH 'http://localhost:3000/users/:id/reactivate' \ -H 'Authorization: Bearer <access-token>'
Response #15
{
"ok": true
}
/users/:id/verifiedMark verified/unverified. Unverified accounts with a prior login cannot request or verify OTPs until re-verified; fresh invites still work.
Request
curl -X PATCH 'http://localhost:3000/users/:id/verified' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"is_verified":0,"reason":"Suspended pending KYC"}'
Body
{
"is_verified": 0,
"reason": "Suspended pending KYC"
}
Response #16
{
"ok": true,
"is_verified": 0
}
/users/:id/approveApprove/revoke a self-signed-up account (dashboard gate). Revoking kills sessions instantly.
Request
curl -X PATCH 'http://localhost:3000/users/:id/approve' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"approved":1}'
Body
{
"approved": 1
}
Response #17
{
"ok": true,
"is_approved": 1
}
/users/:id/roleChange role (instant session revoke, audited old→new).
Request
curl -X PATCH 'http://localhost:3000/users/:id/role' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"roleCode":"accounts","reason":"Moved to accounts team"}'
Body
{
"roleCode": "accounts",
"reason": "Moved to accounts team"
}
Response #18
{
"ok": true
}
/users/:id/phone/requestStep 1 of verified phone change: OTP goes to the NEW number.
Request
curl -X POST 'http://localhost:3000/users/:id/phone/request' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"newPhone":"9822222222","reason":"Number ported"}'
Body
{
"newPhone": "9822222222",
"reason": "Number ported"
}
Response #19
{
"ok": true,
"message": "OTP sent to new number"
}
/users/:id/phone/confirmStep 2: confirm NEW-number OTP (1234 works on localhost).
Request
curl -X POST 'http://localhost:3000/users/:id/phone/confirm' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"newPhone":"9822222222","otp":"1234"}'
Body
{
"newPhone": "9822222222",
"otp": "1234"
}
Response #20
{
"ok": true
}
/users/me/display-nameUser edits own display name (2–100 chars).
Request
curl -X PATCH 'http://localhost:3000/users/me/display-name' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"displayName":"Sharma Fabrics"}'
Body
{
"displayName": "Sharma Fabrics"
}
Response #21
{
"ok": true
}
/users/me/profileAssignment manager or karigar edits own name and contact address (address ≤ 500 chars).
Request
curl -X PATCH 'http://localhost:3000/users/me/profile' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"displayName":"Night Shift Manager","address":"Surat depot"}'
Body
{
"displayName": "Night Shift Manager",
"address": "Surat depot"
}
Response #22
{
"ok": true,
"name": "Night Shift Manager",
"address": "Surat depot"
}
/users/me/phone/requestSelf phone change, step 1: OTP goes to the NEW number (rate-limited). Karigar master phone syncs on confirm.
Request
curl -X POST 'http://localhost:3000/users/me/phone/request' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"newPhone":"9822222222"}'
Body
{
"newPhone": "9822222222"
}
Response #23
{
"ok": true,
"message": "OTP sent to new number"
}
/users/me/phone/confirmSelf phone change, step 2: confirm NEW-number OTP. Verifying the new number revokes all sessions, including the current one.
Request
curl -X POST 'http://localhost:3000/users/me/phone/confirm' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"newPhone":"9822222222","otp":"1234"}'
Body
{
"newPhone": "9822222222",
"otp": "1234"
}
Response #24
{
"ok": true
}
Fabric
/assignments/mine/:idOne job's details for its karigar: source, product/fabric image, pattern + pattern image, per-metre source cost, instructions, sizes with cloth and row totals, plus the karigar-saved pattern cost list. Never includes vendor cost fields.
Request
curl -X GET 'http://localhost:3000/assignments/mine/:id' \ -H 'Authorization: Bearer <access-token>'
Response #25
{
"ok": true,
"job": {
"id": 31,
"assign_no": "ASG-2026-00031",
"qty_base_m": 25,
"status": "ASSIGNED",
"pattern_number": "PAT-101"
},
"sizes": [
{
"size": 24,
"qty": 40,
"cloth": 2.6,
"total_m": 104
}
],
"costs": [
{
"id": 1,
"label": "Stitching",
"value_paise": 4500,
"unit": "Rs",
"qty": 2,
"total_paise": 9000
}
]
}
/assignments/pattern-numbers/:number/costsKarigar saves one cost row (free-text label + numeric value + unit Meter/Rs + whole-piece qty) on a pattern. Row total is value × qty. The same list reappears on every job with that pattern.
Request
curl -X POST 'http://localhost:3000/assignments/pattern-numbers/:number/costs' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"label":"Stitching","value":45,"unit":"Rs","qty":2}'
Body
{
"label": "Stitching",
"value": 45,
"unit": "Rs",
"qty": 2
}
Response #26
{
"ok": true,
"id": 1
}
/assignments/pattern-costs/:idRemove a saved pattern cost row.
Request
curl -X DELETE 'http://localhost:3000/assignments/pattern-costs/:id' \ -H 'Authorization: Bearer <access-token>'
Response #27
{
"ok": true
}
/assignments/patterns?limit=10&offset=0Pattern master list: code + image + cloth per piece + full dynamic costing list each.
Request
curl -X GET 'http://localhost:3000/assignments/patterns?limit=10&offset=0' \ -H 'Authorization: Bearer <access-token>'
Response #28
{
"ok": true,
"limit": 10,
"offset": 0,
"total": 1,
"patterns": [
{
"pattern_number": "PAT-101",
"image_url": "/uploads/patterns/1727....jpg",
"cloth_per_piece_m": 2.5,
"costs": [
{
"id": 1,
"label": "Stitching",
"value_paise": 4500,
"unit": "Rs",
"qty": 2,
"total_paise": 9000
}
]
}
]
}
/assignments/patternsCreate a pattern: code + image required (image via /assignments/pattern-image), optional clothPerPiece fallback (metres, ≤1000), dynamic costs list + per-size cloth list optional. Cost rows are {label, value, unit, qty} (total = value × qty); size rows are {size, cloth} for even sizes 12–40.
Request
curl -X POST 'http://localhost:3000/assignments/patterns' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"code":"PAT-101","imageUrl":"/uploads/patterns/1727....jpg","clothPerPiece":2.5,"costs":[{"label":"Stitching","value":45,"unit":"Rs","qty":2}],"sizes":[{"size":24,"cloth":2.6},{"size":26,"cloth":2.7}]}'
Body
{
"code": "PAT-101",
"imageUrl": "/uploads/patterns/1727....jpg",
"clothPerPiece": 2.5,
"costs": [
{
"label": "Stitching",
"value": 45,
"unit": "Rs",
"qty": 2
}
],
"sizes": [
{
"size": 24,
"cloth": 2.6
},
{
"size": 26,
"cloth": 2.7
}
]
}
Response #29
{
"ok": true,
"code": "PAT-101"
}
/assignments/patterns/:codeUpdate a pattern image, fallback cloth, and/or replace its whole costing list and per-size cloth list. Code is immutable.
Request
curl -X PATCH 'http://localhost:3000/assignments/patterns/:code' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"imageUrl":"/uploads/patterns/1727....jpg","costs":[{"label":"Stitching","value":45,"unit":"Rs","qty":2}],"sizes":[{"size":24,"cloth":2.6}]}'
Body
{
"imageUrl": "/uploads/patterns/1727....jpg",
"costs": [
{
"label": "Stitching",
"value": 45,
"unit": "Rs",
"qty": 2
}
],
"sizes": [
{
"size": 24,
"cloth": 2.6
}
]
}
Response #30
{
"ok": true,
"code": "PAT-101"
}
/assignments/patterns/:codeDelete a pattern + its costs. Blocked (409) while any assignment uses the pattern.
Request
curl -X DELETE 'http://localhost:3000/assignments/patterns/:code' \ -H 'Authorization: Bearer <access-token>'
Response #31
{
"ok": true
}
Suppliers
/fabrics/suppliersCreate supplier master (starts PENDING verification).
Request
curl -X POST 'http://localhost:3000/fabrics/suppliers' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"firmName":"Sharma Fabrics","contact":"R. Sharma","phone":"+919811111111","gstin":"07ABCDE1234F1Z5","address":"Surat","userId":8}'
Body
{
"firmName": "Sharma Fabrics",
"contact": "R. Sharma",
"phone": "+919811111111",
"gstin": "07ABCDE1234F1Z5",
"address": "Surat",
"userId": 8
}
Response #32
{
"ok": true,
"id": 3
}
/fabrics/suppliers?limit=10&offset=0Paginated supplier masters.
Request
curl -X GET 'http://localhost:3000/fabrics/suppliers?limit=10&offset=0' \ -H 'Authorization: Bearer <access-token>'
Response #33
{
"ok": true,
"items": [
{
"id": 3,
"firm_name": "Sharma Fabrics",
"verification_status": "PENDING",
"is_active": 1
}
],
"limit": 10,
"offset": 0,
"total": 12,
"suppliers": [
"...same as items..."
]
}
/fabrics/suppliers/meOwn supplier profile (phone + ID included, read-only).
Request
curl -X GET 'http://localhost:3000/fabrics/suppliers/me' \ -H 'Authorization: Bearer <access-token>'
Response #34
{
"ok": true,
"profile": {
"id": 3,
"firm_name": "Sharma Fabrics",
"contact_person": "R. Sharma",
"phone_e164": "+919811111111",
"email": "a@b.com",
"gstin": "07ABCDE1234F1Z5",
"address": "Surat",
"verification_status": "PENDING"
}
}
/fabrics/suppliers/meUpdate name/contact/address/email/GST. phone + ID rejected (422).
Request
curl -X PATCH 'http://localhost:3000/fabrics/suppliers/me' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"firmName":"Sharma Fabrics Pvt Ltd","email":"ops@sharma.com","gstin":"07ABCDE1234F1Z5","address":"Surat, Gujarat"}'
Body
{
"firmName": "Sharma Fabrics Pvt Ltd",
"email": "ops@sharma.com",
"gstin": "07ABCDE1234F1Z5",
"address": "Surat, Gujarat"
}
Response #35
{
"ok": true,
"profile": {
"id": 3,
"firm_name": "Sharma Fabrics Pvt Ltd",
"email": "ops@sharma.com"
}
}
/fabrics/suppliers/:id/verifyAdmin clears supplier profile (clears the first-login notice).
Request
curl -X PATCH 'http://localhost:3000/fabrics/suppliers/:id/verify' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"decision":"VERIFIED"}'
Body
{
"decision": "VERIFIED"
}
Response #36
{
"ok": true
}
Supplier ledger
/fabrics/suppliers/paymentsRecord a payment made TO a supplier (posts to the ledger's Paid column — cash out clears the payable). Finance roles only — suppliers get 403. Writes an audit row (critical).
Request
curl -X POST 'http://localhost:3000/fabrics/suppliers/payments' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"supplierId":3,"amount":5000,"mode":"Bank","reference":"TXN99","paidOn":"2026-10-01","remarks":"advance"}'
Body
{
"supplierId": 3,
"amount": 5000,
"mode": "Bank",
"reference": "TXN99",
"paidOn": "2026-10-01",
"remarks": "advance"
}
Response #37
{
"ok": true,
"id": 1
}
/fabrics/suppliers/:id/ledger?limit=10&offset=0Statement of account with running balance. bills = APPROVED supplies (goods received); paid = payments made. balance = bills − paid. `:id` may be `me`. Cross-supplier reads by a supplier return 403.
Request
curl -X GET 'http://localhost:3000/fabrics/suppliers/:id/ledger?limit=10&offset=0' \ -H 'Authorization: Bearer <access-token>'
Response #38
{
"ok": true,
"supplier": {
"id": 3,
"firm_name": "Sharma Fabrics"
},
"totals": {
"supplied_paise": 1055000,
"paid_paise": 500000,
"balance_paise": 555000
},
"total": 4,
"limit": 10,
"offset": 0,
"entries": [
{
"dt": "2026-10-01",
"particulars": "Supply SUP-2026-00001",
"bill_paise": 370000,
"paid_paise": 0,
"balance_paise": 370000
},
{
"dt": "2026-10-02",
"particulars": "Payment Bank TXN99",
"bill_paise": 0,
"paid_paise": 500000,
"balance_paise": -130000
}
]
}
/fabrics/suppliers/ledger-summaryAll-supplier outstanding snapshot (balance sheet of payables): supplied vs paid vs owed per supplier, plus grand totals.
Request
curl -X GET 'http://localhost:3000/fabrics/suppliers/ledger-summary' \ -H 'Authorization: Bearer <access-token>'
Response #39
{
"ok": true,
"summary": [
{
"id": 3,
"firm_name": "Sharma Fabrics",
"supplied_paise": 1055000,
"paid_paise": 500000,
"balance_paise": 555000
}
],
"totals": {
"supplied": 6033500,
"paid": 500000,
"balance": 5533500
}
}
Suppliers
/fabrics/karigarsCreate karigar master.
Request
curl -X POST 'http://localhost:3000/fabrics/karigars' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"name":"Mohan","phone":"+919833333333","skillTags":"stitching","capacityM":1000}'
Body
{
"name": "Mohan",
"phone": "+919833333333",
"skillTags": "stitching",
"capacityM": 1000
}
Response #40
{
"ok": true,
"id": 5
}
/fabrics/karigars?limit=10&offset=0Paginated karigar masters.
Request
curl -X GET 'http://localhost:3000/fabrics/karigars?limit=10&offset=0' \ -H 'Authorization: Bearer <access-token>'
Response #41
{
"ok": true,
"items": [
{
"id": 5,
"name": "Mohan",
"is_active": 1,
"capacity_m": 1000
}
],
"limit": 10,
"offset": 0,
"total": 30,
"karigars": [
"...same as items..."
]
}
/fabrics/karigars/:idRename a karigar master (2–150 chars). Names set here appear in assignment karigar dropdowns.
Request
curl -X PATCH 'http://localhost:3000/fabrics/karigars/:id' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"name":"Mohan"}'
Body
{
"name": "Mohan"
}
Response #42
{
"ok": true,
"id": 5,
"name": "Mohan"
}
Vendors
/vendorsCreate an external processing vendor with one or more services. processing accepts comma-separated names; unknown names become active catalogue entries and are linked automatically.
Request
curl -X POST 'http://localhost:3000/vendors' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"name":"Fine Thread Works","phone":"+919811234567","address":"Surat","processing":"Embroidery, Lace"}'
Body
{
"name": "Fine Thread Works",
"phone": "+919811234567",
"address": "Surat",
"processing": "Embroidery, Lace"
}
Response #43
{
"ok": true,
"id": 1,
"name": "Fine Thread Works",
"processing": [
{
"id": 1,
"name": "Embroidery"
},
{
"id": 2,
"name": "Lace"
}
]
}
/vendors?limit=10&offset=0Paginated external vendors with their offered processing services.
Request
curl -X GET 'http://localhost:3000/vendors?limit=10&offset=0' \ -H 'Authorization: Bearer <access-token>'
Response #44
{
"ok": true,
"items": [
{
"id": 1,
"name": "Fine Thread Works",
"services": "Embroidery, Lace"
}
],
"limit": 10,
"offset": 0,
"total": 1,
"vendors": [
"...same as items..."
]
}
/vendors/processing-types?limit=10&offset=0Dynamic processing catalogue used by vendor and cost forms.
Request
curl -X GET 'http://localhost:3000/vendors/processing-types?limit=10&offset=0' \ -H 'Authorization: Bearer <access-token>'
Response #45
{
"ok": true,
"items": [
{
"id": 1,
"name": "Embroidery"
},
{
"id": 2,
"name": "Lace"
}
],
"limit": 10,
"offset": 0,
"total": 2,
"processingTypes": [
"...same as items..."
]
}
/vendors/processing-typesExplicitly add a processing catalogue entry. Existing names return the existing ID.
Request
curl -X POST 'http://localhost:3000/vendors/processing-types' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"name":"Printing"}'
Body
{
"name": "Printing"
}
Response #46
{
"ok": true,
"id": 3,
"name": "Printing"
}
Supplies
/supplies/fabric-typesActive fabric names for the form dropdown (managed master).
Request
curl -X GET 'http://localhost:3000/supplies/fabric-types' \ -H 'Authorization: Bearer <access-token>'
Response #47
{
"ok": true,
"fabrics": [
"Cotton",
"Rayon",
"Viscose",
"...31 total..."
]
}
/suppliesAdd a single-product supply. images_0..N gallery (up to 100 images, 720p-optimized) + items JSON with one line per image, each with its own quantity [{quantity, unit, price, taxPct?, tax?, extraCharges?, fabric?}]. Legacy image/images[] mapping still accepted. Fabric validated against master (optional). Status auto 1 In review; header total = sum of lines.
Request
curl -X POST 'http://localhost:3000/supplies' \
-H 'Authorization: Bearer <access-token>' \
-F 'images_0=@a.jpg' \
-F 'images_1=@b.jpg' \
-F 'items=[{"quantity":10' \
-F '"unit":"M"=' \
-F '"price":100=' \
-F '"taxPct":12}=' \
-F '{"quantity":20=' \
-F '"unit":"M"=' \
-F '"price":100}]='
Response #48
{
"ok": true,
"id": 11,
"supplyNo": "SUP-2026-00011",
"status": 1,
"status_label": "In review",
"total_paise": 112000,
"image_url": "/uploads/supplies/1727....jpg",
"items_count": 1,
"images_count": 2
}
/supplies?limit=10&offset=0&status=Paginated supplies (own only for suppliers; staff may add supplierId/status). Status: 1 In review, 2 Approved, 3 Change Request, 4 Rejected — single or comma list (?status=2,3,4) for decided views.
Request
curl -X GET 'http://localhost:3000/supplies?limit=10&offset=0&status=' \ -H 'Authorization: Bearer <access-token>'
Response #49
{
"ok": true,
"items": [
{
"id": 11,
"supply_no": "SUP-2026-00011",
"quantity": 50,
"unit": "M",
"total_paise": 606000,
"status": 1,
"status_label": "In review"
}
],
"limit": 10,
"offset": 0,
"total": 4,
"supplies": [
"...same as items..."
]
}
/supplies/:idSupply detail + per-product gallery images + full supplier block + Ammikids company block + status history (ownership-checked for suppliers). Powers the printable invoice popup.
Request
curl -X GET 'http://localhost:3000/supplies/:id' \ -H 'Authorization: Bearer <access-token>'
Response #50
{
"ok": true,
"supply": {
"id": 11,
"supply_no": "SUP-2026-00011",
"status": 1,
"status_label": "In review"
},
"items": [
{
"line_no": 1,
"quantity": 10,
"unit": "M",
"total_paise": 112000,
"images": [
"/uploads/supplies/a.jpg",
"/uploads/supplies/b.jpg"
]
}
],
"history": []
}
/supplies/:idEdit while In review only: per-line quantities + shared unit/price/taxPct/extra/fabric + remarks. Totals recomputed. Photos lock at submit (delete + resubmit to change photos).
Request
curl -X PATCH 'http://localhost:3000/supplies/:id' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"lines":[{"id":40,"quantity":20}],"price":200}'
Body
{
"lines": [
{
"id": 40,
"quantity": 20
}
],
"price": 200
}
Response #51
{
"ok": true,
"total_paise": 525000
}
/supplies/:idWithdraw an In-review supply (cascades lines + gallery). Decided supplies cannot be deleted.
Request
curl -X DELETE 'http://localhost:3000/supplies/:id' \ -H 'Authorization: Bearer <access-token>'
Response #52
{
"ok": true
}
/supplies/:id/statusReview supply: 2 Approved, 3/4 need reason. Only from 1/3.
Request
curl -X PATCH 'http://localhost:3000/supplies/:id/status' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"status":2}'
Body
{
"status": 2
}
Response #53
{
"ok": true,
"status": 2,
"status_label": "Approved"
}
Fabric
/fabricsCreate draft fabric entry with lots. Charges apportioned pro-rata; totals in paise.
Request
curl -X POST 'http://localhost:3000/fabrics' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"supplierId":3,"invoiceNo":"INV-101","entryDate":"2026-09-20","receivedDate":"2026-09-21","taxMode":"EXCLUSIVE","taxRate":5,"freight":200,"lots":[{"fabricName":"Cotton","fabricCode":"CT-40","colour":"Red","lotNo":"L1","qty":500,"unit":"M","rate":120}]}'
Body
{
"supplierId": 3,
"invoiceNo": "INV-101",
"entryDate": "2026-09-20",
"receivedDate": "2026-09-21",
"taxMode": "EXCLUSIVE",
"taxRate": 5,
"freight": 200,
"lots": [
{
"fabricName": "Cotton",
"fabricCode": "CT-40",
"colour": "Red",
"lotNo": "L1",
"qty": 500,
"unit": "M",
"rate": 120
}
]
}
Response #54
{
"ok": true,
"id": 21,
"entryNo": "RF-2026-00021"
}
/fabrics/:id/submitSubmit draft (needs invoice attachment). → SUBMITTED.
Request
curl -X POST 'http://localhost:3000/fabrics/:id/submit' \ -H 'Authorization: Bearer <access-token>'
Response #55
{
"ok": true
}
/fabrics?limit=10&offset=0Paginated entries (own firm for suppliers; cost hidden for karigars).
Request
curl -X GET 'http://localhost:3000/fabrics?limit=10&offset=0' \ -H 'Authorization: Bearer <access-token>'
Response #56
{
"ok": true,
"items": [
{
"id": 21,
"entry_no": "RF-2026-00021",
"status": "SUBMITTED"
}
],
"limit": 10,
"offset": 0,
"total": 9,
"entries": [
"...same as items..."
]
}
/fabrics/:idEntry + lots + status history + stock ledger.
Request
curl -X GET 'http://localhost:3000/fabrics/:id' \ -H 'Authorization: Bearer <access-token>'
Response #57
{
"ok": true,
"entry": {
"id": 21,
"status": "VERIFIED"
},
"lots": [],
"history": [],
"ledger": []
}
/verificationsVerify a lot (item-level). verifiedQtyM ≤ received; reason mandatory on shortage. Cannot verify own supply (SoD).
Request
curl -X POST 'http://localhost:3000/verifications' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"lotId":55,"verifiedQtyM":485,"defectCode":"DAMAGED","qcGrade":"B","reason":"15m water damage","priceFlag":"ok"}'
Body
{
"lotId": 55,
"verifiedQtyM": 485,
"defectCode": "DAMAGED",
"qcGrade": "B",
"reason": "15m water damage",
"priceFlag": "ok"
}
Response #58
{
"ok": true
}
/verifications/:lotId/correctionSend lot back to supplier (max 3 cycles, then escalated).
Request
curl -X POST 'http://localhost:3000/verifications/:lotId/correction' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"reason":"Invoice qty mismatch, recheck roll L1"}'
Body
{
"reason": "Invoice qty mismatch, recheck roll L1"
}
Response #59
{
"ok": true
}
/samplesCut sample from VERIFIED lot (deducts stock in-txn).
Request
curl -X POST 'http://localhost:3000/samples' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"lotId":55,"qtyM":2,"purpose":"Colour approval"}'
Body
{
"lotId": 55,
"qtyM": 2,
"purpose": "Colour approval"
}
Response #60
{
"ok": true,
"id": 9,
"sampleNo": "S-2026-00009"
}
/samples/approveCOO approve/reject (no self-approval; delegates allowed). Rejection needs reason. Unlocks the lot for assignment.
Request
curl -X POST 'http://localhost:3000/samples/approve' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"sampleId":9,"decision":"APPROVED"}'
Body
{
"sampleId": 9,
"decision": "APPROVED"
}
Response #61
{
"ok": true
}
/samples/pending?limit=10&offset=0Paginated COO approval queue.
Request
curl -X GET 'http://localhost:3000/samples/pending?limit=10&offset=0' \ -H 'Authorization: Bearer <access-token>'
Response #62
{
"ok": true,
"items": [
{
"id": 9,
"sample_no": "S-2026-00009",
"lot_no": "L1"
}
],
"limit": 10,
"offset": 0,
"total": 2,
"samples": [
"...same as items..."
]
}
/assignments/approved-supplies?limit=10&offset=0All approved supplies with remaining allocation balances and AVAILABLE/PARTIALLY_ALLOCATED/FULLY_ALLOCATED status.
Request
curl -X GET 'http://localhost:3000/assignments/approved-supplies?limit=10&offset=0' \ -H 'Authorization: Bearer <access-token>'
Response #63
{
"ok": true,
"items": [
{
"id": 2,
"supply_no": "SUP-2026-00002",
"received_qty_m": 10,
"available_qty_m": 9,
"allocation_status": "PARTIALLY_ALLOCATED"
}
],
"limit": 10,
"offset": 0,
"total": 4,
"supplies": [
"...same as items..."
]
}
/assignments/pattern-imageUpload one pattern reference image (multipart field `image`, jpg/png/webp). Returns the URL to send as patternImageUrl.
Request
curl -X POST 'http://localhost:3000/assignments/pattern-image' \ -H 'Authorization: Bearer <access-token>' \ -F 'image=@pattern.jpg'
Response #64
{
"ok": true,
"imageUrl": "/uploads/patterns/1727....jpg"
}
/assignments/pattern-numbers?q=&limit=10&offset=0Saved pattern numbers with their images, for autocomplete while typing a pattern number.
Request
curl -X GET 'http://localhost:3000/assignments/pattern-numbers?q=&limit=10&offset=0' \ -H 'Authorization: Bearer <access-token>'
Response #65
{
"ok": true,
"items": [
{
"pattern_number": "PAT-101",
"image_url": "/uploads/patterns/1727....jpg"
}
],
"limit": 10,
"offset": 0,
"total": 1,
"patterns": [
"...same as items..."
]
}
/assignmentsAssign approved-supply qty to karigar (row-locked; over-allocation 422; capacity overload warns via overload:true). Supply allocations decrement the same supply for later use. supplyItemId allocates from one product line and decrements both line and header. Optional sizes is a piece breakdown using even sizes 12–40; when sizes and a pattern with cloth-per-piece are given, pieces × cloth must fit the assigned metres (else 422 Assigned Cloth is Less); patternNumber replaces the dashboard job reference; patternImageUrl (from /assignments/pattern-image) saves/refreshes the pattern library image.
Request
curl -X POST 'http://localhost:3000/assignments' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"supplyId":2,"supplyItemId":2,"karigarId":5,"qty":150,"unit":"M","expectedReturnDate":"2026-10-05","patternNumber":"PAT-101","patternImageUrl":"/uploads/patterns/1727....jpg","sizes":[{"size":24,"qty":40},{"size":26,"qty":35}]}'
Body
{
"supplyId": 2,
"supplyItemId": 2,
"karigarId": 5,
"qty": 150,
"unit": "M",
"expectedReturnDate": "2026-10-05",
"patternNumber": "PAT-101",
"patternImageUrl": "/uploads/patterns/1727....jpg",
"sizes": [
{
"size": 24,
"qty": 40
},
{
"size": 26,
"qty": 35
}
]
}
Response #66
{
"ok": true,
"id": 31,
"assignNo": "ASG-2026-00031",
"overload": false
}
/assignments?limit=10&offset=0&status=&karigarId=Paginated assignment list for managers (status/karigar filters; source=supply|lot filter; includes lot- and supply-sourced rows with source labels).
Request
curl -X GET 'http://localhost:3000/assignments?limit=10&offset=0&status=&karigarId=' \ -H 'Authorization: Bearer <access-token>'
Response #67
{
"ok": true,
"items": [
{
"id": 31,
"assign_no": "ASG-2026-00031",
"karigar_name": "Mohan",
"status": "ASSIGNED"
}
],
"limit": 10,
"offset": 0,
"total": 1,
"assignments": [
"...same as items..."
]
}
/assignments/:id/ackAck receipt (handover photo proof; managers proxy-ack flagged). Or refuse with refusalReason (qty restored).
Request
curl -X POST 'http://localhost:3000/assignments/:id/ack' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"handoverPhotoUrl":"/uploads/handover/1.jpg"}'
Body
{
"handoverPhotoUrl": "/uploads/handover/1.jpg"
}
Response #68
{
"ok": true
}
/assignments/:id/cancelCancel assignment (qty restored from ASSIGNED/ACKED/IN_PROCESS/CHALLAN_PENDING, audited).
Request
curl -X POST 'http://localhost:3000/assignments/:id/cancel' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"reason":"Karigar unavailable"}'
Body
{
"reason": "Karigar unavailable"
}
Response #69
{
"ok": true
}
/assignments/:id/challanSubmit challan with final lines. All cloth rows and pattern cost lines stay on the challan; paidSizes/paidCostIds mark per-line Paid flags (unchecked by default). ASSIGNED/ACKED/IN_PROCESS → CHALLAN_PENDING.
Request
curl -X POST 'http://localhost:3000/assignments/:id/challan' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"paidSizes":[24],"paidCostIds":[3],"note":"Advance received"}'
Body
{
"paidSizes": [
24
],
"paidCostIds": [
3
],
"note": "Advance received"
}
Response #70
{
"ok": true
}
/assignments/challan-pendingChallan approval queue: jobs with sizes+cloth, pattern costs, exclusions, and per-metre rate.
Request
curl -X GET 'http://localhost:3000/assignments/challan-pending' \ -H 'Authorization: Bearer <access-token>'
Response #71
{
"ok": true,
"limit": 10,
"offset": 0,
"total": 1,
"challans": [
{
"id": 31,
"assign_no": "ASG-2026-00031",
"status": "CHALLAN_PENDING"
}
]
}
/assignments/:id/challan-decideDecide a pending challan: APPROVED → CHALLAN_APPROVED (terminal), REJECTED → IN_PROCESS (reason mandatory).
Request
curl -X POST 'http://localhost:3000/assignments/:id/challan-decide' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"decision":"APPROVED"}'
Body
{
"decision": "APPROVED"
}
Response #72
{
"ok": true
}
/assignments/:id/processKarigar starts production: ASSIGNED/ACKED → IN_PROCESS (qty stays reserved, audited). Powers the Challan button in the job popup.
Request
curl -X POST 'http://localhost:3000/assignments/:id/process' \ -H 'Authorization: Bearer <access-token>'
Response #73
{
"ok": true
}
/assignments/:id/processing-costsSave an external-vendor processing charge on an active assignment. processingTypeId must be offered by the vendor; custom processing names are created and linked automatically. Qty is whole pieces in NOs/PCs, rate is Rs per piece, patternNumber is required, and amount is calculated server-side as qty x rate.
Request
curl -X POST 'http://localhost:3000/assignments/:id/processing-costs' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"vendorId":1,"processing":"Embroidery","qty":25,"unit":"PCs","rate":50,"patternNumber":"PAT-101"}'
Body
{
"vendorId": 1,
"processing": "Embroidery",
"qty": 25,
"unit": "PCs",
"rate": 50,
"patternNumber": "PAT-101"
}
Response #74
{
"ok": true,
"id": 1,
"totals": {
"entries": 1,
"material_paise": 150000,
"processing_paise": 125000,
"total_paise": 275000
}
}
/assignments/:id/processing-costsSaved processing-cost entries and charge totals for one assignment.
Request
curl -X GET 'http://localhost:3000/assignments/:id/processing-costs' \ -H 'Authorization: Bearer <access-token>'
Response #75
{
"ok": true,
"entries": [
{
"id": 1,
"vendor_name": "Fine Thread Works",
"processing_name": "Embroidery",
"qty": 25,
"unit": "PCs",
"rate_paise": 5000,
"amount_paise": 125000,
"pattern_number": "PAT-101"
}
],
"totals": {
"entries": 1,
"processing_paise": 125000,
"vendor_payable_paise": 125000
}
}
/assignments/:id/cost-summaryMaterial plus processing rollup for future end-product costing. pro-rates the source material by assigned metres and includes the assigned entry image when available. Closed assignments are excluded from product cost while vendor charges remain payable.
Request
curl -X GET 'http://localhost:3000/assignments/:id/cost-summary' \ -H 'Authorization: Bearer <access-token>'
Response #76
{
"ok": true,
"assignment": {
"id": 31,
"assign_no": "ASG-2026-00031",
"qty_base_m": 25,
"pattern_number": "PAT-101",
"source": {
"kind": "supply",
"ref": "SUP-2026-00011",
"image": "/uploads/supplies/a.jpg"
}
},
"sizes": [
{
"size": 24,
"qty": 40
},
{
"size": 26,
"qty": 35
}
],
"totals": {
"material_paise": 150000,
"processing_paise": 125000,
"total_paise": 275000,
"vendor_payable_paise": 125000,
"included_in_product_cost": true
}
}
/assignments/mine/list?limit=10&offset=0Karigar's own jobs, paginated.
Request
curl -X GET 'http://localhost:3000/assignments/mine/list?limit=10&offset=0' \ -H 'Authorization: Bearer <access-token>'
Response #77
{
"ok": true,
"items": [
{
"id": 31,
"assign_no": "ASG-2026-00031",
"status": "ASSIGNED"
}
],
"limit": 10,
"offset": 0,
"total": 6,
"assignments": [
"...same as items..."
]
}
/delegatesGrant time-boxed COO approval cover.
Request
curl -X POST 'http://localhost:3000/delegates' \
-H 'Authorization: Bearer <access-token>' \
-H 'Content-Type: application/json' \
-d '{"delegateUserId":12,"days":7}'
Body
{
"delegateUserId": 12,
"days": 7
}
Response #78
{
"ok": true
}
Reports
/reports/stock?limit=10&offset=0Per-lot received/verified/available.
Request
curl -X GET 'http://localhost:3000/reports/stock?limit=10&offset=0' \ -H 'Authorization: Bearer <access-token>'
Response #79
{
"ok": true,
"items": [
{
"lot_id": 55,
"received": 500,
"verified": 485,
"available": 333
}
],
"limit": 10,
"offset": 0,
"total": 40,
"stock": [
"...same as items..."
]
}
/reports/available?limit=10&offset=0COO-approved lots with qty left to assign.
Request
curl -X GET 'http://localhost:3000/reports/available?limit=10&offset=0' \ -H 'Authorization: Bearer <access-token>'
Response #80
{
"ok": true,
"items": [],
"limit": 10,
"offset": 0,
"total": 0,
"lots": []
}
/reports/overdue?limit=10&offset=0Unverified >48h + unacked >72h.
Request
curl -X GET 'http://localhost:3000/reports/overdue?limit=10&offset=0' \ -H 'Authorization: Bearer <access-token>'
Response #81
{
"ok": true,
"limit": 10,
"offset": 0,
"overdueVerification": [],
"unackedAssignments": []
}
/reports/cost-summary?limit=10&offset=0Landed cost per entry (recomputable from components).
Request
curl -X GET 'http://localhost:3000/reports/cost-summary?limit=10&offset=0' \ -H 'Authorization: Bearer <access-token>'
Response #82
{
"ok": true,
"items": [
{
"entry_no": "RF-2026-00021",
"total_m": 500,
"total_rs": 63000,
"per_m_rs": 126
}
],
"limit": 10,
"offset": 0,
"total": 9,
"summary": [
"...same as items..."
]
}
/reports/lot/:id/timelineFull lot timeline: ledger + history + audit.
Request
curl -X GET 'http://localhost:3000/reports/lot/:id/timeline' \ -H 'Authorization: Bearer <access-token>'
Response #83
{
"ok": true,
"lot": {},
"ledger": [],
"history": [],
"audit": []
}
/audit?limit=10&offset=0&entity=&id=Audit trail, optionally filtered by entity (e.g. ?entity=supply&id=11).
Request
curl -X GET 'http://localhost:3000/audit?limit=10&offset=0&entity=&id=' \ -H 'Authorization: Bearer <access-token>'
Response #84
{
"ok": true,
"items": [
{
"action": "supply_created",
"entity_type": "supply",
"entity_id": "11"
}
],
"limit": 10,
"offset": 0,
"total": 120,
"audit": [
"...same as items..."
]
}
Sales
/sales/customers?q=9876&limit=8&offset=0Search customers by name or phone (autocomplete for the sales form).
Request
curl -X GET 'http://localhost:3000/sales/customers?q=9876&limit=8&offset=0'
Response #85
{
"ok": true,
"items": [
{
"id": 1,
"name": "ABC Customer",
"phone_e164": "+919876543210"
}
],
"limit": 8,
"offset": 0,
"total": 1,
"customers": [
"...same as items..."
]
}
/sales/customersAdd a customer. Contact number required and unique — 409 when it already exists. Address: area/city text, state from the all-India list, 6-digit pincode.
Request
curl -X POST 'http://localhost:3000/sales/customers' \
-H 'Content-Type: application/json' \
-d '{"name":"ABC Customer","phone":"9876543210","area":"Ring Road","city":"Surat","state":"Gujarat","pincode":"395002"}'
Body
{
"name": "ABC Customer",
"phone": "9876543210",
"area": "Ring Road",
"city": "Surat",
"state": "Gujarat",
"pincode": "395002"
}
Response #86
{
"ok": true,
"id": 1,
"name": "ABC Customer",
"phone": "+919876543210",
"area": "Ring Road",
"city": "Surat",
"state": "Gujarat",
"pincode": "395002"
}
/sales/next-invoiceAdvisory next invoice number for the form header (authoritative number is assigned on save).
Request
curl -X GET 'http://localhost:3000/sales/next-invoice'
Response #87
{
"ok": true,
"invoiceNo": "INV-0001"
}
/sales/invoicesSave a sale: customerId or name/phone (+ optional details) plus 1–100 item rows {pcs, rate}. Amounts auto-computed; unknown phones create the customer, known phones reuse it. Optional: gstType IGST (5%) or CGST_SGST (2.5+2.5, default for Maharashtra), discountType percent/flat/per_piece + discountValue (discount on subtotal, GST on the net), agentId (type Agent) + commType percent (of net) / flat (per pc x pcs).
Request
curl -X POST 'http://localhost:3000/sales/invoices' \
-H 'Content-Type: application/json' \
-d '{"date":"2026-10-15","customerId":1,"items":[{"pcs":2,"rate":100},{"pcs":4,"rate":200}],"gstType":"IGST","discountType":"percent","discountValue":10,"agentId":2,"commType":"percent","commValue":2}'
Body
{
"date": "2026-10-15",
"customerId": 1,
"items": [
{
"pcs": 2,
"rate": 100
},
{
"pcs": 4,
"rate": 200
}
],
"gstType": "IGST",
"discountType": "percent",
"discountValue": 10,
"agentId": 2,
"commType": "percent",
"commValue": 2
}
Response #88
{
"ok": true,
"id": 1,
"invoiceNo": "INV-0001",
"totalPcs": 6,
"totalPaise": 100000
}
/sales/invoices?q=ABC&limit=20&offset=0Sales history — search by invoice number, customer name, phone, or exact date (&date=YYYY-MM-DD).
Request
curl -X GET 'http://localhost:3000/sales/invoices?q=ABC&limit=20&offset=0'
Response #89
{
"ok": true,
"items": [
{
"invoice_no": "INV-0001",
"customer_name": "ABC Customer",
"total_pcs": 12,
"total_paise": 280000
}
],
"limit": 20,
"offset": 0,
"total": 1,
"sales": [
"...same as items..."
]
}
/sales/invoices/:idOne sale with its item lines (used for View / Edit / Print).
Request
curl -X GET 'http://localhost:3000/sales/invoices/:id'
Response #90
{
"ok": true,
"invoice": {
"invoice_no": "INV-0001",
"total_pcs": 12,
"total_paise": 280000
},
"items": [
{
"lineNo": 1,
"pcs": 2,
"ratePaise": 10000,
"amountPaise": 20000
}
]
}
/sales/invoices/:idEdit date, customer snapshot, item rows (pcs/rate plus per-line discountType percent/flat + discountValue), bill discount, GST, agent/commission. Totals recomputed.
Request
curl -X PATCH 'http://localhost:3000/sales/invoices/:id' \
-H 'Content-Type: application/json' \
-d '{"items":[{"pcs":3,"rate":150,"discountType":"percent","discountValue":10}],"gstType":"CGST_SGST"}'
Body
{
"items": [
{
"pcs": 3,
"rate": 150,
"discountType": "percent",
"discountValue": 10
}
],
"gstType": "CGST_SGST"
}
Response #91
{
"ok": true
}
/sales/invoices/:idDelete a sale and its lines (client confirms first).
Request
curl -X DELETE 'http://localhost:3000/sales/invoices/:id'
Response #92
{
"ok": true,
"deleted": 1
}
/sales/customers/:id/ledgerCustomer statement — sales (bill), GR returns and payments received in one date-wise list with running balance. Balance = billed − returned − received.
Request
curl -X GET 'http://localhost:3000/sales/customers/:id/ledger'
Response #93
{
"ok": true,
"entries": [
{
"kind": "sale",
"date": "2026-10-15",
"ref": "INV-0001",
"pcs": 12,
"billPaise": 280000,
"type": "sale",
"receivedPaise": 0,
"mop": null,
"account": null,
"balancePaise": 280000
}
],
"totals": {
"billedPaise": 280000,
"returnedPaise": 0,
"receivedPaise": 0,
"balancePaise": 280000
}
}
/sales/accountsShop cash/bank accounts (payment destinations).
Request
curl -X GET 'http://localhost:3000/sales/accounts'
Response #94
{
"ok": true,
"items": [
{
"id": 1,
"name": "Main Cash"
}
],
"accounts": [
"...same as items..."
]
}
/sales/accountsAdd an account — same fields as the customer form (name + phone/address/note). type General/Cash/Bank/Agent (agents earn commission).
Request
curl -X POST 'http://localhost:3000/sales/accounts' \
-H 'Content-Type: application/json' \
-d '{"name":"Agent Raju","type":"Agent","phone":"9811111111"}'
Body
{
"name": "Agent Raju",
"type": "Agent",
"phone": "9811111111"
}
Response #95
{
"ok": true,
"id": 2,
"name": "Agent Raju",
"type": "Agent"
}
/sales/paymentsRecord money received: receipt auto-numbered (RCPT-), MOP Cash/Bank, optional To-account and optional against-bill link (invoiceId).
Request
curl -X POST 'http://localhost:3000/sales/payments' \
-H 'Content-Type: application/json' \
-d '{"customerId":1,"amount":500,"mop":"Cash","accountId":1,"invoiceId":3}'
Body
{
"customerId": 1,
"amount": 500,
"mop": "Cash",
"accountId": 1,
"invoiceId": 3
}
Response #96
{
"ok": true,
"id": 1,
"receiptNo": "RCPT-0001"
}
/sales/payments/:idDelete a received entry (balances update).
Request
curl -X DELETE 'http://localhost:3000/sales/payments/:id'
Response #97
{
"ok": true,
"deleted": 1
}
/sales/returnsGoods return (GR): auto-numbered (GR-), optional link to a bill, reduces what the customer owes.
Request
curl -X POST 'http://localhost:3000/sales/returns' \
-H 'Content-Type: application/json' \
-d '{"customerId":1,"invoiceId":3,"pcs":2,"amount":200}'
Body
{
"customerId": 1,
"invoiceId": 3,
"pcs": 2,
"amount": 200
}
Response #98
{
"ok": true,
"id": 1,
"grNo": "GR-0001"
}
/sales/returns/:idDelete a GR entry (balances update).
Request
curl -X DELETE 'http://localhost:3000/sales/returns/:id'
Response #99
{
"ok": true,
"deleted": 1
}
/sales/accounts/:id/ledgerAgent statement — commission earned per bill minus payouts, oldest first with running balance payable.
Request
curl -X GET 'http://localhost:3000/sales/accounts/:id/ledger'
Response #100
{
"ok": true,
"entries": [
{
"kind": "commission",
"date": "2026-10-15",
"ref": "INV-0001",
"commPaise": 1800,
"paidPaise": 0,
"balancePaise": 1800
}
],
"totals": {
"earnedPaise": 1800,
"paidPaise": 0,
"balancePaise": 1800
}
}
/sales/agent-payoutsPayout to an Agent account: auto-numbered (AGP-), MOP Cash/Bank.
Request
curl -X POST 'http://localhost:3000/sales/agent-payouts' \
-H 'Content-Type: application/json' \
-d '{"accountId":2,"amount":500,"mop":"Cash"}'
Body
{
"accountId": 2,
"amount": 500,
"mop": "Cash"
}
Response #101
{
"ok": true,
"id": 1,
"payoutNo": "AGP-0001"
}
/sales/agent-payouts/:idDelete a payout (balances update).
Request
curl -X DELETE 'http://localhost:3000/sales/agent-payouts/:id'
Response #102
{
"ok": true,
"deleted": 1
}