Skip to main content

API Reference

Base URL

EnvironmentURL
Productionhttps://loftygoldenoil.com/api
Staginghttp://170.9.14.60:8443/api

All endpoints are relative to the base URL above. The API follows REST conventions: resources are nouns, HTTP methods indicate operations.

Content-Type

All request and response bodies use application/json.

Authentication

The API uses JSON Web Token (JWT) Bearer tokens for authentication.

  1. Authenticate via POST /api/auth/login with email and password.
  2. Include the returned token in all subsequent requests:
    Authorization: Bearer <token>
  3. Tokens expire after 24 hours. Re-authentication is required upon expiry (no refresh tokens).

Response Format

All responses return JSON with the following structure:

Success (single resource):

{
"id": 1,
"name": "Item Name",
"created_at": "2026-01-15T10:30:00.000Z"
}

Success (collection):

[
{ "id": 1, "name": "Item 1" },
{ "id": 2, "name": "Item 2" }
]

Error:

{
"error": "Error message describing what went wrong"
}

HTTP Status Codes

CodeMeaning
200OK — Request succeeded
201Created — Resource created successfully
400Bad Request — Invalid input or missing required fields
401Unauthorized — Missing or invalid authentication token
403Forbidden — Authenticated but insufficient permissions
404Not Found — Resource does not exist
409Conflict — Duplicate resource or constraint violation
500Internal Server Error — Unexpected server failure

Rate Limiting

Rate limiting is not currently implemented. It is recommended for production hardening. A suggested configuration:

  • Authenticated requests: 100 requests per minute per user
  • Login endpoint: 10 attempts per minute per IP

Authentication Endpoints

POST /api/auth/login

Authenticate a user and receive a JWT token.

PropertyValue
Auth RequiredNo
Rate LimitRecommended: 10 req/min/IP

Request Body:

{
"email": "user@example.com",
"password": "securepassword"
}
FieldTypeRequiredDescription
emailstringYesUser's login email
passwordstringYesUser's password

Success Response (200):

{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 1,
"name": "John Doe",
"email": "user@example.com",
"role": "admin"
}
}

Error Response (401):

{
"error": "Invalid email or password"
}

GET /api/auth/me

Retrieve the currently authenticated user's profile.

PropertyValue
Auth RequiredYes (any role)

Request Headers:

Authorization: Bearer <token>

Success Response (200):

{
"id": 1,
"name": "John Doe",
"email": "user@example.com",
"role": "admin",
"status": "active",
"created_at": "2026-01-15T10:30:00.000Z"
}

Error Response (401):

{
"error": "Unauthorized"
}

POST /api/auth/forgot-password

Send a password reset email to the specified address.

PropertyValue
Auth RequiredNo

Request Body:

{
"email": "user@example.com"
}
FieldTypeRequiredDescription
emailstringYesRegistered email address

Success Response (200):

{
"message": "If an account exists with that email, a password reset link has been sent"
}

Notes:

  • Always returns success message regardless of whether the email exists (prevents email enumeration).
  • Reset email is sent via Resend HTTP API from noreply@loftygoldenoil.com.
  • Reset tokens are time-limited and single-use.

POST /api/auth/reset-password

Reset a user's password using a valid reset token.

PropertyValue
Auth RequiredNo

Request Body:

{
"token": "reset-token-from-email",
"password": "newsecurepassword"
}
FieldTypeRequiredDescription
tokenstringYesPassword reset token from email
passwordstringYesNew password

Success Response (200):

{
"message": "Password has been reset successfully"
}

Error Response (400):

{
"error": "Invalid or expired reset token"
}

User Management Endpoints

GET /api/users

List all users.

PropertyValue
Auth RequiredYes (admin only)

Request Headers:

Authorization: Bearer <token>

Success Response (200):

[
{
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"role": "admin",
"status": "active",
"created_at": "2026-01-15T10:30:00.000Z"
}
]

POST /api/users

Create a new user account.

PropertyValue
Auth RequiredYes (admin only)

Request Body:

{
"name": "New User",
"email": "newuser@example.com",
"password": "securepassword",
"role": "sales",
"status": "active"
}
FieldTypeRequiredDescription
namestringYesFull name
emailstringYesUnique email address
passwordstringYesInitial password (min 6 chars recommended)
rolestringYesOne of: admin, production, sales, accountant
statusstringNoactive (default) or inactive

Success Response (201):

{
"id": 5,
"name": "New User",
"email": "newuser@example.com",
"role": "sales",
"status": "active"
}

Error Response (409):

{
"error": "Email already exists"
}

PUT /api/users/:id

Update an existing user account.

PropertyValue
Auth RequiredYes (admin only)

URL Parameters:

ParameterTypeDescription
idintegerUser ID

Request Body:

{
"name": "Updated Name",
"role": "accountant",
"status": "inactive",
"password": "newpassword"
}
FieldTypeRequiredDescription
namestringNoUpdated name
emailstringNoUpdated email
rolestringNoUpdated role
statusstringNoactive or inactive
passwordstringNoNew password (only if changing)

Success Response (200):

{
"id": 5,
"name": "Updated Name",
"email": "newuser@example.com",
"role": "accountant",
"status": "inactive"
}

Error Response (404):

{
"error": "User not found"
}

DELETE /api/users/:id

Delete a user account. Cannot delete your own account.

PropertyValue
Auth RequiredYes (admin only)

URL Parameters:

ParameterTypeDescription
idintegerUser ID

Success Response (200):

{
"message": "User deleted successfully"
}

Error Response (400):

{
"error": "Cannot delete your own account"
}

Inventory Endpoints

GET /api/inventory

List inventory items with optional filtering.

PropertyValue
Auth RequiredYes (any role)

Query Parameters:

ParameterTypeDescription
searchstringSearch by name or SKU
categorystringFilter by category
statusstringActive or Inactive

Example: GET /api/inventory?search=oil&category=Edible%20Oil&status=Active

Success Response (200):

[
{
"id": 1,
"name": "Palm Oil 5L",
"sku": "EO-PLM-005L",
"category": "Edible Oil",
"unit_of_measure": "Litres",
"current_stock": 1250.00,
"minimum_stock_level": 500.00,
"unit_cost": 3200.00,
"selling_price": 4500.00,
"status": "Active",
"description": "Premium refined palm oil",
"created_at": "2026-01-15T10:30:00.000Z",
"updated_at": "2026-01-20T14:22:00.000Z"
}
]

POST /api/inventory

Create a new inventory item.

PropertyValue
Auth RequiredYes (admin, production)

Request Body:

{
"name": "Soybean Oil 1L",
"sku": "EO-SOY-001L",
"category": "Edible Oil",
"unit_of_measure": "Litres",
"current_stock": 500.00,
"minimum_stock_level": 200.00,
"unit_cost": 2800.00,
"selling_price": 3800.00,
"status": "Active",
"description": "Refined soybean oil"
}
FieldTypeRequiredDescription
namestringYesItem name
skustringYesUnique SKU code
categorystringYesOne of: Edible Oil, Packaging, Raw Material, Equipment, Supplies
unit_of_measurestringYesOne of: Litres, Kg, Cartons, Pieces, Rolls, Sachets
current_stocknumberNoInitial stock (default 0)
minimum_stock_levelnumberNoReorder threshold (default 0)
unit_costnumberNoCost per unit (default 0)
selling_pricenumberNoSelling price (default 0)
statusstringNoActive (default) or Inactive
descriptionstringNoItem description

Success Response (201):

{
"id": 3,
"name": "Soybean Oil 1L",
"sku": "EO-SOY-001L",
"category": "Edible Oil",
"unit_of_measure": "Litres",
"current_stock": 500.00,
"minimum_stock_level": 200.00,
"unit_cost": 2800.00,
"selling_price": 3800.00,
"status": "Active"
}

Error Response (409):

{
"error": "SKU already exists"
}

PUT /api/inventory/:id

Update an existing inventory item.

PropertyValue
Auth RequiredYes (admin, production)

URL Parameters:

ParameterTypeDescription
idintegerInventory item ID

Request Body: Same fields as POST, all optional.

Success Response (200): Updated inventory object.

Error Response (404):

{
"error": "Inventory item not found"
}

DELETE /api/inventory/:id

Delete an inventory item. Fails if item has linked purchases, sales, or adjustments.

PropertyValue
Auth RequiredYes (admin, production)

URL Parameters:

ParameterTypeDescription
idintegerInventory item ID

Success Response (200):

{
"message": "Inventory item deleted successfully"
}

Error Response (409):

{
"error": "Cannot delete inventory item with existing transactions"
}

POST /api/inventory/:id/adjust

Create a manual inventory adjustment.

PropertyValue
Auth RequiredYes (admin, production)

URL Parameters:

ParameterTypeDescription
idintegerInventory item ID

Request Body:

{
"adjustment_quantity": -25.50,
"reason": "Damaged goods during transport",
"notes": "Photos documented",
"adjustment_date": "2026-03-15"
}
FieldTypeRequiredDescription
adjustment_quantitynumberYesPositive to add stock, negative to reduce
reasonstringYesReason for adjustment
notesstringNoAdditional details
adjustment_datestringNoDate (ISO format, default today)

Success Response (201):

{
"message": "Inventory adjustment recorded successfully",
"adjustment": {
"id": 12,
"inventory_id": 1,
"adjustment_quantity": -25.50,
"reason": "Damaged goods during transport",
"adjustment_date": "2026-03-15",
"created_by": 1
},
"new_stock": 1224.50
}

GET /api/inventory/alerts

Get all inventory items currently at or below their minimum stock level.

PropertyValue
Auth RequiredYes (any role)

Success Response (200):

[
{
"id": 3,
"name": "Soybean Oil 1L",
"sku": "EO-SOY-001L",
"current_stock": 150.00,
"minimum_stock_level": 200.00,
"unit_of_measure": "Litres",
"shortage": 50.00
}
]

Purchases Endpoints

GET /api/purchases

List all purchase records.

PropertyValue
Auth RequiredYes (any role)

Query Parameters:

ParameterTypeDescription
inventory_idintegerFilter by inventory item
start_datestringFilter from date (ISO)
end_datestringFilter to date (ISO)

Success Response (200):

[
{
"id": 1,
"inventory_id": 1,
"inventory_name": "Palm Oil 5L",
"supplier_name": "West Africa Palm Products",
"quantity": 500.00,
"unit_cost": 3200.00,
"total_cost": 1600000.00,
"purchase_date": "2026-02-01",
"notes": "Monthly restock",
"created_at": "2026-02-01T09:00:00.000Z"
}
]

POST /api/purchases

Record a new purchase.

PropertyValue
Auth RequiredYes (admin, production)

Request Body:

{
"inventory_id": 1,
"supplier_name": "West Africa Palm Products",
"quantity": 500.00,
"unit_cost": 3200.00,
"total_cost": 1600000.00,
"purchase_date": "2026-02-01",
"notes": "Monthly restock"
}
FieldTypeRequiredDescription
inventory_idintegerYesInventory item purchased
supplier_namestringYesSupplier name
quantitynumberYesQuantity purchased
unit_costnumberYesCost per unit
total_costnumberYesTotal cost
purchase_datestringNoISO date string
notesstringNoNotes

Success Response (201):

{
"id": 25,
"inventory_id": 1,
"supplier_name": "West Africa Palm Products",
"quantity": 500.00,
"unit_cost": 3200.00,
"total_cost": 1600000.00,
"purchase_date": "2026-02-01"
}

Notes: Creating a purchase automatically increases inventory.current_stock by the purchased quantity.


PUT /api/purchases/:id

Update a purchase record.

PropertyValue
Auth RequiredYes (admin, production)

URL Parameters:

ParameterTypeDescription
idintegerPurchase ID

Request Body: Same fields as POST, all optional.

Success Response (200): Updated purchase object.


DELETE /api/purchases/:id

Delete a purchase record.

PropertyValue
Auth RequiredYes (admin, production)

URL Parameters:

ParameterTypeDescription
idintegerPurchase ID

Success Response (200):

{
"message": "Purchase deleted successfully"
}

Notes: Deleting a purchase reverses the stock increase on the associated inventory item.


Production Endpoints

GET /api/production

List production runs with optional date filtering.

PropertyValue
Auth RequiredYes (any role)

Query Parameters:

ParameterTypeDescription
start_datestringFilter from date (ISO)
end_datestringFilter to date (ISO)
statusstringFilter by status

Success Response (200):

[
{
"id": 10,
"product_name": "Refined Palm Oil",
"start_date": "2026-03-01",
"end_date": "2026-03-03",
"batches": 5,
"input_volume": 2500.00,
"output_volume": 2200.00,
"input_cost": 8000000.00,
"output_cost": 9900000.00,
"yield_percent": 88.00,
"cost_per_litre": 4500.00,
"status": "Completed",
"notes": "Batch processed successfully",
"created_at": "2026-03-01T08:00:00.000Z"
}
]

POST /api/production

Create a new production run.

PropertyValue
Auth RequiredYes (admin, production)

Request Body:

{
"product_name": "Refined Palm Oil",
"start_date": "2026-03-01",
"end_date": "2026-03-03",
"batches": 5,
"input_volume": 2500.00,
"output_volume": 2200.00,
"input_cost": 8000000.00,
"output_cost": 9900000.00,
"yield_percent": 88.00,
"cost_per_litre": 4500.00,
"status": "Completed",
"notes": "Batch processed successfully"
}
FieldTypeRequiredDescription
product_namestringYesProduct name
start_datestringYesISO date
end_datestringNoISO date
batchesintegerNoNumber of batches
input_volumenumberNoInput material volume
output_volumenumberNoOutput product volume
input_costnumberNoTotal input cost
output_costnumberNoTotal output cost
yield_percentnumberNoYield efficiency percentage
cost_per_litrenumberNoCost per litre of output
statusstringNoOne of: Planned, In Progress, Completed, Delayed
notesstringNoProduction notes

Success Response (201): Created production run object.


PUT /api/production/:id

Update an existing production run.

PropertyValue
Auth RequiredYes (admin, production)

URL Parameters:

ParameterTypeDescription
idintegerProduction run ID

Request Body: Same fields as POST, all optional.

Success Response (200): Updated production run object.


DELETE /api/production/:id

Delete a production run.

PropertyValue
Auth RequiredYes (admin, production)

URL Parameters:

ParameterTypeDescription
idintegerProduction run ID

Success Response (200):

{
"message": "Production run deleted successfully"
}

Sales Endpoints

GET /api/sales

List sales with optional filtering.

PropertyValue
Auth RequiredYes (any role)

Query Parameters:

ParameterTypeDescription
customer_idintegerFilter by customer
inventory_idintegerFilter by product
statusstringpending, completed, cancelled
start_datestringFilter from date (ISO)
end_datestringFilter to date (ISO)

Success Response (200):

[
{
"id": 45,
"customer_id": 3,
"customer_name": "ABC Enterprises",
"inventory_id": 1,
"inventory_name": "Palm Oil 5L",
"quantity": 50.00,
"unit_price": 4500.00,
"total_amount": 225000.00,
"sale_date": "2026-03-10",
"status": "completed",
"notes": "Delivered to Ikeja warehouse",
"created_at": "2026-03-10T11:00:00.000Z"
}
]

POST /api/sales

Record a new sale.

PropertyValue
Auth RequiredYes (admin, sales)

Request Body:

{
"customer_id": 3,
"inventory_id": 1,
"quantity": 50.00,
"unit_price": 4500.00,
"total_amount": 225000.00,
"sale_date": "2026-03-10",
"status": "completed",
"notes": "Delivered to Ikeja warehouse"
}
FieldTypeRequiredDescription
customer_idintegerYesCustomer ID
inventory_idintegerYesInventory item sold
quantitynumberYesQuantity sold
unit_pricenumberYesPrice per unit
total_amountnumberYesTotal sale amount
sale_datestringNoISO date
statusstringNopending, completed, cancelled
notesstringNoNotes

Success Response (201): Created sale object.

Notes: Creating a completed sale automatically decreases inventory.current_stock by the sold quantity.


PUT /api/sales/:id

Update an existing sale.

PropertyValue
Auth RequiredYes (admin, sales)

URL Parameters:

ParameterTypeDescription
idintegerSale ID

Request Body: Same fields as POST, all optional.

Success Response (200): Updated sale object.


DELETE /api/sales/:id

Delete a sale record.

PropertyValue
Auth RequiredYes (admin, sales)

URL Parameters:

ParameterTypeDescription
idintegerSale ID

Success Response (200):

{
"message": "Sale deleted successfully"
}

Notes: Deleting a sale with completed status reverses the stock deduction on the associated inventory item.


Customer Endpoints

GET /api/customers

List all customers.

PropertyValue
Auth RequiredYes (any role)

Query Parameters:

ParameterTypeDescription
searchstringSearch by company name or contact
statusstringActive or Inactive

Success Response (200):

[
{
"id": 3,
"company_name": "ABC Enterprises",
"contact_person": "Adebayo Johnson",
"phone": "+234-801-234-5678",
"email": "adebayo@abc-enterprises.com",
"address": "15 Broad Street",
"city": "Lagos",
"state": "Lagos",
"credit_limit": 5000000.00,
"outstanding_balance": 1200000.00,
"status": "Active",
"notes": "Regular customer",
"created_at": "2026-01-15T10:30:00.000Z"
}
]

POST /api/customers

Create a new customer.

PropertyValue
Auth RequiredYes (admin, sales)

Request Body:

{
"company_name": "XYZ Distributors",
"contact_person": "Chioma Okafor",
"phone": "+234-802-345-6789",
"email": "chioma@xyz-dist.com",
"address": "22 Marina Road",
"city": "Lagos",
"state": "Lagos",
"credit_limit": 3000000.00,
"outstanding_balance": 0.00,
"status": "Active",
"notes": "New distributor"
}
FieldTypeRequiredDescription
company_namestringYesCompany name
contact_personstringNoContact name
phonestringNoPhone number
emailstringNoEmail
addressstringNoAddress
citystringNoCity
statestringNoState
credit_limitnumberNoCredit limit (default 0)
outstanding_balancenumberNoOutstanding balance (default 0)
statusstringNoActive (default) or Inactive
notesstringNoNotes

Success Response (201): Created customer object.


PUT /api/customers/:id

Update an existing customer.

PropertyValue
Auth RequiredYes (admin, sales)

URL Parameters:

ParameterTypeDescription
idintegerCustomer ID

Request Body: Same fields as POST, all optional.

Success Response (200): Updated customer object.


DELETE /api/customers/:id

Delete a customer. Fails if customer has existing sales.

PropertyValue
Auth RequiredYes (admin, sales)

URL Parameters:

ParameterTypeDescription
idintegerCustomer ID

Success Response (200):

{
"message": "Customer deleted successfully"
}

Error Response (409):

{
"error": "Cannot delete customer with existing sales"
}

Supplier Endpoints

GET /api/suppliers

List all suppliers.

PropertyValue
Auth RequiredYes (any role)

Query Parameters:

ParameterTypeDescription
searchstringSearch by company name
statusstringActive or Inactive

Success Response (200):

[
{
"id": 2,
"company_name": "West Africa Palm Products",
"contact_person": "Emeka Nwosu",
"phone": "+234-803-456-7890",
"email": "emeka@wapp.com",
"address": "Port Harcourt Industrial Zone",
"city": "Port Harcourt",
"state": "Rivers",
"status": "Active",
"notes": "Primary palm oil supplier",
"created_at": "2026-01-15T10:30:00.000Z"
}
]

POST /api/suppliers

Create a new supplier.

PropertyValue
Auth RequiredYes (admin)

Request Body:

{
"company_name": "Nigerian Packaging Solutions",
"contact_person": "Funke Adeyemi",
"phone": "+234-804-567-8901",
"email": "funke@npack.com",
"address": "Ogba Industrial Estate",
"city": "Lagos",
"state": "Lagos",
"status": "Active",
"notes": "Packaging materials supplier"
}
FieldTypeRequiredDescription
company_namestringYesCompany name
contact_personstringNoContact name
phonestringNoPhone
emailstringNoEmail
addressstringNoAddress
citystringNoCity
statestringNoState
statusstringNoActive (default) or Inactive
notesstringNoNotes

Success Response (201): Created supplier object.


PUT /api/suppliers/:id

Update an existing supplier.

PropertyValue
Auth RequiredYes (admin)

URL Parameters:

ParameterTypeDescription
idintegerSupplier ID

Request Body: Same fields as POST, all optional.

Success Response (200): Updated supplier object.


DELETE /api/suppliers/:id

Delete a supplier. Fails if supplier has existing purchases.

PropertyValue
Auth RequiredYes (admin)

URL Parameters:

ParameterTypeDescription
idintegerSupplier ID

Success Response (200):

{
"message": "Supplier deleted successfully"
}

Expenses Endpoints

GET /api/expenses

List expenses with optional filtering.

PropertyValue
Auth RequiredYes (any role)

Query Parameters:

ParameterTypeDescription
statusstringpending, approved, rejected
categorystringFilter by category
start_datestringFilter from date (ISO)
end_datestringFilter to date (ISO)

Success Response (200):

[
{
"id": 8,
"category": "Transport",
"description": "Fuel for delivery truck",
"amount": 45000.00,
"payment_method": "Cash",
"status": "approved",
"vendor": "Total Fuel Station",
"reference_number": "FUEL-2026-001",
"notes": "Monthly fuel top-up",
"expense_date": "2026-03-12",
"created_at": "2026-03-12T08:30:00.000Z"
}
]

POST /api/expenses

Create a new expense.

PropertyValue
Auth RequiredYes (admin, accountant)

Request Body:

{
"category": "Utilities",
"description": "Electricity bill - March 2026",
"amount": 125000.00,
"payment_method": "Transfer",
"status": "pending",
"vendor": "Ikeja Electric",
"reference_number": "IE-2026-03-001",
"notes": "Factory electricity",
"expense_date": "2026-03-15"
}
FieldTypeRequiredDescription
categorystringYesExpense category
descriptionstringYesDescription
amountnumberYesAmount
payment_methodstringYesCash, Transfer, Cheque, Card
statusstringNopending (default), approved, rejected
vendorstringNoVendor name
reference_numberstringNoTransaction reference
notesstringNoNotes
expense_datestringNoISO date

Success Response (201): Created expense object.


PUT /api/expenses/:id

Update an existing expense.

PropertyValue
Auth RequiredYes (admin, accountant)

URL Parameters:

ParameterTypeDescription
idintegerExpense ID

Request Body: Same fields as POST, all optional.

Success Response (200): Updated expense object.


DELETE /api/expenses/:id

Delete an expense.

PropertyValue
Auth RequiredYes (admin, accountant)

URL Parameters:

ParameterTypeDescription
idintegerExpense ID

Success Response (200):

{
"message": "Expense deleted successfully"
}

Accounts Receivable Endpoints

GET /api/ar/invoices

List AR invoices with optional filtering.

PropertyValue
Auth RequiredYes (any role)

Query Parameters:

ParameterTypeDescription
statusstringpending, paid, overdue, partial
customer_idintegerFilter by customer
start_datestringFilter from date
end_datestringFilter to date

Success Response (200):

[
{
"id": 15,
"sale_id": 45,
"invoice_number": "INV-2026-0015",
"amount": 225000.00,
"status": "pending",
"due_date": "2026-04-10",
"notes": "Net 30 terms",
"created_at": "2026-03-10T11:00:00.000Z",
"customer_name": "ABC Enterprises"
}
]

POST /api/ar/invoices

Create a new AR invoice.

PropertyValue
Auth RequiredYes (admin, sales)

Request Body:

{
"sale_id": 45,
"invoice_number": "INV-2026-0015",
"amount": 225000.00,
"status": "pending",
"due_date": "2026-04-10",
"notes": "Net 30 terms"
}
FieldTypeRequiredDescription
sale_idintegerYesRelated sale ID
invoice_numberstringYesUnique invoice number
amountnumberYesInvoice amount
statusstringNopending (default), paid, overdue, partial
due_datestringYesPayment due date (ISO)
notesstringNoNotes

Success Response (201): Created invoice object.


PUT /api/ar/invoices/:id

Update an existing invoice.

PropertyValue
Auth RequiredYes (admin, sales)

URL Parameters:

ParameterTypeDescription
idintegerInvoice ID

Request Body: Same fields as POST, all optional.

Success Response (200): Updated invoice object.


DELETE /api/ar/invoices/:id

Delete an invoice. Fails if payments have been recorded against it.

PropertyValue
Auth RequiredYes (admin, sales)

URL Parameters:

ParameterTypeDescription
idintegerInvoice ID

Success Response (200):

{
"message": "Invoice deleted successfully"
}

POST /api/ar/invoices/:id/send-email

Send the invoice to the customer via email.

PropertyValue
Auth RequiredYes (admin, sales)

URL Parameters:

ParameterTypeDescription
idintegerInvoice ID

Success Response (200):

{
"message": "Invoice sent successfully"
}

Notes: Uses Resend HTTP API to send a formatted invoice email from noreply@loftygoldenoil.com to the customer's email address.


GET /api/ar/payments

List all AR payments.

PropertyValue
Auth RequiredYes (any role)

Query Parameters:

ParameterTypeDescription
invoice_idintegerFilter by invoice
start_datestringFilter from date
end_datestringFilter to date

Success Response (200):

[
{
"id": 20,
"invoice_id": 15,
"amount": 100000.00,
"payment_method": "Transfer",
"reference_number": "TRF-2026-001",
"payment_date": "2026-03-25",
"notes": "Partial payment",
"created_at": "2026-03-25T14:00:00.000Z"
}
]

POST /api/ar/payments

Record a new payment against an invoice.

PropertyValue
Auth RequiredYes (admin, sales)

Request Body:

{
"invoice_id": 15,
"amount": 100000.00,
"payment_method": "Transfer",
"reference_number": "TRF-2026-001",
"payment_date": "2026-03-25",
"notes": "Partial payment"
}
FieldTypeRequiredDescription
invoice_idintegerYesInvoice ID
amountnumberYesPayment amount
payment_methodstringYesCash, Transfer, Cheque, Card
reference_numberstringNoTransaction reference
payment_datestringNoISO date
notesstringNoNotes

Success Response (201):

{
"id": 20,
"invoice_id": 15,
"amount": 100000.00,
"payment_method": "Transfer",
"reference_number": "TRF-2026-001",
"payment_date": "2026-03-25",
"message": "Payment recorded. Invoice status updated."
}

Notes: Recording a payment updates the related invoice's status (e.g., partial, paid) and the customer's outstanding_balance.


DELETE /api/ar/payments/:id

Delete a payment record.

PropertyValue
Auth RequiredYes (admin, sales)

URL Parameters:

ParameterTypeDescription
idintegerPayment ID

Success Response (200):

{
"message": "Payment deleted successfully"
}

Notes: Deleting a payment reverses the invoice status update and adjusts the customer's outstanding balance.


Accounting Endpoints

GET /api/accounting/gl

Retrieve General Ledger entries.

PropertyValue
Auth RequiredYes (accountant)

Query Parameters:

ParameterTypeDescription
start_datestringFilter from date (ISO)
end_datestringFilter to date (ISO)
account_codestringFilter by account code

Success Response (200):

[
{
"id": 100,
"entry_date": "2026-03-15",
"account_code": "1001",
"account_name": "Cash",
"debit": 0.00,
"credit": 225000.00,
"description": "Sale to ABC Enterprises - INV-2026-0015",
"reference": "SALE-45",
"created_at": "2026-03-15T11:00:00.000Z"
}
]

GET /api/accounting/trial-balance

Generate a trial balance report.

PropertyValue
Auth RequiredYes (accountant)

Query Parameters:

ParameterTypeDescription
as_of_datestringDate for the trial balance (ISO)

Success Response (200):

{
"as_of_date": "2026-03-31",
"accounts": [
{
"account_code": "1001",
"account_name": "Cash",
"debit": 1500000.00,
"credit": 0.00,
"balance": 1500000.00
},
{
"account_code": "2001",
"account_name": "Accounts Payable",
"debit": 0.00,
"credit": 800000.00,
"balance": -800000.00
}
],
"total_debit": 1500000.00,
"total_credit": 1500000.00,
"is_balanced": true
}

GET /api/accounting/profit-loss

Generate a Profit & Loss statement.

PropertyValue
Auth RequiredYes (accountant)

Query Parameters:

ParameterTypeDescription
start_datestringPeriod start (ISO)
end_datestringPeriod end (ISO)

Success Response (200):

{
"period": {
"start_date": "2026-01-01",
"end_date": "2026-03-31"
},
"revenue": 25000000.00,
"cost_of_goods_sold": 18000000.00,
"gross_profit": 7000000.00,
"operating_expenses": 3500000.00,
"net_profit": 3500000.00
}

GET /api/accounting/balance-sheet

Generate a Balance Sheet report.

PropertyValue
Auth RequiredYes (accountant)

Query Parameters:

ParameterTypeDescription
as_of_datestringDate for the balance sheet (ISO)

Success Response (200):

{
"as_of_date": "2026-03-31",
"assets": {
"current_assets": 12000000.00,
"fixed_assets": 25000000.00,
"total_assets": 37000000.00
},
"liabilities": {
"current_liabilities": 5000000.00,
"long_term_liabilities": 10000000.00,
"total_liabilities": 15000000.00
},
"equity": 22000000.00,
"total_liabilities_and_equity": 37000000.00
}

POST /api/accounting/journal-entries

Create a new journal entry.

PropertyValue
Auth RequiredYes (accountant)

Request Body:

{
"entry_date": "2026-03-15",
"description": "Record sale to ABC Enterprises",
"lines": [
{
"account_code": "1001",
"debit": 225000.00,
"credit": 0.00
},
{
"account_code": "4001",
"debit": 0.00,
"credit": 225000.00
}
]
}
FieldTypeRequiredDescription
entry_datestringYesDate of the entry
descriptionstringYesEntry description
linesarrayYesAt least 2 lines (debit + credit)
lines[].account_codestringYesAccount code
lines[].debitnumberYesDebit amount
lines[].creditnumberYesCredit amount

Success Response (201):

{
"id": 200,
"entry_date": "2026-03-15",
"description": "Record sale to ABC Enterprises",
"lines": [
{ "account_code": "1001", "debit": 225000.00, "credit": 0.00 },
{ "account_code": "4001", "debit": 0.00, "credit": 225000.00 }
],
"total_debit": 225000.00,
"total_credit": 225000.00
}

Error Response (400):

{
"error": "Total debits must equal total credits"
}

GET /api/accounting/journal-entries

List journal entries.

PropertyValue
Auth RequiredYes (accountant)

Query Parameters:

ParameterTypeDescription
start_datestringFilter from date
end_datestringFilter to date

Success Response (200): Array of journal entry objects with their lines.


POST /api/accounting/journal-entries/:id/reverse

Reverse an existing journal entry.

PropertyValue
Auth RequiredYes (accountant)

URL Parameters:

ParameterTypeDescription
idintegerJournal entry ID

Success Response (201): New reversing journal entry object.

Notes: Creates a new journal entry with debits and credits swapped from the original. The original entry is marked as reversed.


POST /api/accounting/vouchers

Create a new payment voucher.

PropertyValue
Auth RequiredYes (accountant)

Request Body:

{
"voucher_number": "PV-2026-0042",
"voucher_type": "payment",
"payee": "West Africa Palm Products",
"amount": 1600000.00,
"payment_method": "Transfer",
"description": "Payment for palm oil purchase - PO-2026-025",
"voucher_date": "2026-03-20",
"approved_by": "John Doe"
}
FieldTypeRequiredDescription
voucher_numberstringYesUnique voucher number
voucher_typestringYespayment or receipt
payeestringYesPayee name
amountnumberYesVoucher amount
payment_methodstringYesPayment method
descriptionstringYesDescription
voucher_datestringYesISO date
approved_bystringNoApprover name

Success Response (201): Created voucher object.


GET /api/accounting/vouchers

List payment vouchers.

PropertyValue
Auth RequiredYes (accountant)

Query Parameters:

ParameterTypeDescription
voucher_typestringpayment or receipt
start_datestringFilter from date
end_datestringFilter to date

Success Response (200): Array of voucher objects.


Dashboard Endpoints

GET /api/dashboard/stats

Retrieve key dashboard statistics.

PropertyValue
Auth RequiredYes (any role)

Success Response (200):

{
"total_sales_this_month": 8500000.00,
"total_sales_last_month": 7200000.00,
"total_purchases_this_month": 5000000.00,
"pending_invoices": 12,
"overdue_invoices": 3,
"low_stock_items": 5,
"total_customers": 45,
"total_inventory_value": 35000000.00
}

GET /api/dashboard/charts

Retrieve chart data for dashboard visualizations.

PropertyValue
Auth RequiredYes (any role)

Query Parameters:

ParameterTypeDescription
periodstringdaily, weekly, monthly

Success Response (200):

{
"sales_trend": [
{ "date": "2026-03-01", "amount": 250000.00 },
{ "date": "2026-03-02", "amount": 320000.00 }
],
"production_output": [
{ "month": "2026-01", "volume": 12000.00 },
{ "month": "2026-02", "volume": 14500.00 }
],
"category_breakdown": [
{ "category": "Edible Oil", "value": 15000000.00 },
{ "category": "Packaging", "value": 5000000.00 }
]
}

GET /api/dashboard/alerts

Retrieve system alerts and notifications.

PropertyValue
Auth RequiredYes (any role)

Success Response (200):

{
"low_stock_alerts": [
{
"item": "Palm Oil 5L",
"sku": "EO-PLM-005L",
"current_stock": 150,
"minimum_stock": 500,
"shortage": 350
}
],
"overdue_invoices": [
{
"invoice_number": "INV-2026-0012",
"customer": "XYZ Distributors",
"amount": 450000.00,
"due_date": "2026-03-01",
"days_overdue": 29
}
],
"pending_requisitions": 3
}

Activity Endpoints

GET /api/activity

Retrieve audit log entries.

PropertyValue
Auth RequiredYes (admin)

Query Parameters:

ParameterTypeDescription
start_datestringFilter from date (ISO)
end_datestringFilter to date (ISO)
entity_typestringFilter by entity type
actionstringFilter by action type
limitintegerNumber of entries (default 100)

Success Response (200):

[
{
"id": 1500,
"user_id": 1,
"user_name": "John Doe",
"action": "CREATE",
"entity_type": "sale",
"entity_id": 45,
"details": "Created sale: 50 units of Palm Oil 5L to ABC Enterprises",
"ip_address": "192.168.1.100",
"created_at": "2026-03-10T11:00:00.000Z"
}
]

Reports Endpoints

GET /api/reports/consolidated

Generate a consolidated business report.

PropertyValue
Auth RequiredYes (any role)

Query Parameters:

ParameterTypeDescription
start_datestringReport period start (ISO)
end_datestringReport period end (ISO)

Success Response (200):

{
"period": { "start_date": "2026-01-01", "end_date": "2026-03-31" },
"sales_summary": { "total": 25000000.00, "count": 120 },
"purchase_summary": { "total": 18000000.00, "count": 35 },
"expense_summary": { "total": 3500000.00, "count": 48 },
"production_summary": { "total_runs": 15, "total_output": 45000.00 },
"accounts_receivable": { "total_outstanding": 5000000.00, "overdue": 1200000.00 },
"inventory_summary": { "total_value": 35000000.00, "items": 85 }
}

GET /api/customers/:id/statement

Generate a customer account statement.

PropertyValue
Auth RequiredYes (any role)

URL Parameters:

ParameterTypeDescription
idintegerCustomer ID

Query Parameters:

ParameterTypeDescription
start_datestringStatement period start (ISO)
end_datestringStatement period end (ISO)

Success Response (200):

{
"customer": {
"id": 3,
"company_name": "ABC Enterprises",
"credit_limit": 5000000.00,
"outstanding_balance": 1200000.00
},
"period": { "start_date": "2026-01-01", "end_date": "2026-03-31" },
"opening_balance": 800000.00,
"transactions": [
{
"date": "2026-03-10",
"type": "sale",
"reference": "SALE-45",
"description": "Palm Oil 5L x 50",
"debit": 225000.00,
"credit": 0.00,
"balance": 1025000.00
},
{
"date": "2026-03-25",
"type": "payment",
"reference": "TRF-2026-001",
"description": "Bank transfer",
"debit": 0.00,
"credit": 100000.00,
"balance": 925000.00
}
],
"closing_balance": 925000.00
}