OAuth2 Authentication and Setup
Published on March 22, 2026 (Updated July 1, 2026)
WorkStack Core API – OAuth 2.0 Integration Guide
Version 2.0
WorkStack – The Operating System for Business
1. Introduction
WorkStack Core is a complete business management platform that stores user data (inventory, sales, clients, invoices, etc.). Third‑party applications can request access to a user's data using OAuth 2.0. WorkStack handles authentication, authorization, and data storage. Premium users gain access to advanced analytics.
1.1 Key Concepts
- Resource Owner – the WorkStack user who owns the data.
- Client – your application (e.g., Rikizy, a mobile POS, an accounting tool).
- Authorization Server – WorkStack's OAuth server (
workstack.com.ng/oauth). - Resource Server – WorkStack's API endpoints (
api_core.php,api_cloud.php). - Access Token – a short‑lived token (24h) that authorizes API requests.
- Refresh Token – a long‑lived token used to obtain a new access token without user interaction.
1.2 Benefits of Using OAuth
- Users never share their WorkStack password with your application.
- You request only the permissions (scopes) your app needs.
- Tokens can be revoked at any time by the user.
- Analytics features require a premium subscription, which WorkStack enforces.
2. OAuth 2.0 Authorization Code Flow – Step by Step
2.1 Register Your Application
Before you can start, you must register your application with WorkStack. Provide:
- Application name
- Redirect URI (the exact URL where users are sent after authorization)
- List of requested scopes (you can request more than needed, but only ask for what you require)
After registration, you will receive:
- Client ID (public identifier)
- Client Secret (confidential, never expose it in client‑side code)
2.2 Obtain an Authorization Code
Direct the user to the authorization endpoint:
GET https://workstack.com.ng/oauth/authorize.php
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=YOUR_REDIRECT_URI
&scope=SCOPES
&state=STATE
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
response_type | string | yes | Must be code. |
client_id | string | yes | Your client ID. |
redirect_uri | string | yes | Must match the registered URI exactly (including trailing slash). |
scope | string | yes | Space‑separated list of scopes (e.g., profile.read sales.read inventory.read). |
state | string | recommended | A random string your app generates to prevent CSRF. It will be returned in the callback. |
Example:
https://workstack.com.ng/oauth/authorize.php?response_type=code&client_id=rikizy_prod&redirect_uri=https%3A%2F%2Frikizy.com%2Foauth%2Fcallback&scope=profile.read%20sales.read%20inventory.read&state=abc123xyz
The user will be prompted to log in to WorkStack (if not already) and grant the requested permissions. After approval, WorkStack redirects to your redirect_uri with two query parameters:
code– the authorization code (one‑time use, expires in 10 minutes)state– the original state value
2.3 Exchange Authorization Code for Tokens
Your server must exchange the code for an access token and a refresh token.
POST to https://workstack.com.ng/oauth/token.php with form data:
grant_type=authorization_code
client_id=YOUR_CLIENT_ID
client_secret=YOUR_CLIENT_SECRET
code=AUTHORIZATION_CODE
redirect_uri=YOUR_REDIRECT_URI
Response (success):
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 86400,
"refresh_token": "def50200...",
"scope": "profile.read sales.read inventory.read"
}
access_token– used in API requests.expires_in– lifetime in seconds (24 hours).refresh_token– used to obtain a new access token without user interaction.
Store these tokens securely, associated with the user. Never expose the access token to the client side.
2.4 Use the Access Token
Include the token in the Authorization header of every API request:
Authorization: Bearer YOUR_ACCESS_TOKEN
2.5 Refresh the Access Token
When the access token expires (after 86400 seconds), use the refresh token to get a new one.
POST to https://workstack.com.ng/oauth/token.php:
grant_type=refresh_token
client_id=YOUR_CLIENT_ID
client_secret=YOUR_CLIENT_SECRET
refresh_token=YOUR_REFRESH_TOKEN
Response:
{
"access_token": "new_access_token",
"token_type": "Bearer",
"expires_in": 86400,
"refresh_token": "new_refresh_token"
}
Replace the stored token pair with the new values.
3. Scopes and Permissions
Scopes define what your application is allowed to do. Request only the scopes you need.
| Scope | Description | API Actions |
|---|---|---|
profile.read |
Read user profile (business name, etc.) | – |
sales.read |
Read sales records, analytics, forecasts | sales, get_sale_details, analytics_summary, sales_trend, top_products, payment_methods, get_insights, customer_metrics, inventory_turnover, sales_forecast, cash_flow, profit_margin |
sales.write |
Create, edit, void sales | record_sale, clear_debt, void_sale |
inventory.read |
Read inventory items | inventory, search_items, get_item_variants, get_item_details, get_variants |
inventory.write |
Create, edit, delete inventory items | add_item, edit_item, delete_item, save_variant, generate_variants, delete_variant |
clients.read |
Read client ledger | get_clients_json, get_client_by_id, get_client_debts |
clients.write |
Create, edit, delete clients | add_client, edit_client, delete_client |
invoices.read |
Read invoices | list_invoices, get_invoice (via api_cloud.php) |
invoices.write |
Create, edit, delete invoices | save_invoice_draft, delete_invoice (via api_cloud.php) |
reports.read |
Generate and download reports | export_clients_csv, export_sales_csv, export_inventory_csv, sales_report_pdf, export_clients_pdf, export_inventory_pdf, export_full_data, logs |
expenses.read |
Read expense records | list_expenses |
expenses.write |
Create, edit expenses | add_expense, edit_expense |
bundles.read |
Read bundle definitions | get_bundles, get_bundle_details, get_bundle_items |
bundles.write |
Create, edit, delete bundles | save_bundle, delete_bundle |
suppliers.read |
Read supplier list | list_suppliers, list_suppliers_for_select |
suppliers.write |
Create, edit, delete suppliers | add_supplier, edit_supplier, delete_supplier |
deliveries.read |
Read delivery records and tracking | list_deliveries, get_delivery, get_delivery_by_code |
deliveries.write |
Create, update, manage deliveries | create_delivery, update_delivery, update_delivery_status, complete_delivery_with_code, return_delivery, delete_delivery, add_carrier, list_carriers, delete_carrier, add_rider, list_riders, delete_rider |
import.data |
Import clients and inventory via CSV/JSON | import_clients, import_inventory, import_data, download_client_template, download_product_template |
Note: All analytics actions (analytics_summary, sales_trend, etc.) require the user to have an active premium subscription. If they do not, the API returns 403 Forbidden with a message to upgrade.
4. API Endpoints – Detailed Reference
All endpoints are accessed via https://workstack.com.ng/api_core.php (or api_cloud.php for invoices). Use the action parameter to specify the operation.
4.1 Core Business Analytics (Premium Only)
These endpoints provide insights into business performance. They all require the sales.read scope and a premium subscription.
4.1.1 analytics_summary
Description: Get basic counts, revenue, expenses, and profit for a given period.
Use cases: Dashboard overview, period‑over‑period comparison, executive summaries.
HTTP Method: GET or POST
Endpoint: api_core.php?action=analytics_summary
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
range | string | no | One of: today, yesterday, week, month, year, custom. Default: month. |
start_date | date | if range=custom | Start date in YYYY-MM-DD. |
end_date | date | if range=custom | End date in YYYY-MM-DD. |
Example request:
GET https://workstack.com.ng/api_core.php?action=analytics_summary&range=month
Authorization: Bearer <token>
Example response:
{
"success": true,
"client_count": 42,
"product_count": 127,
"revenue": 1250000,
"expenses": 350000,
"profit": 900000
}
Error codes:
401– missing/invalid token403– premium required or insufficient scope500– server error
4.1.2 sales_trend
Description: Get sales totals grouped by month (last 12 months or custom range).
Use cases: Trend charts, year‑over‑year analysis, forecasting.
Parameters: Same as analytics_summary.
Example response:
{
"success": true,
"trend": [
{ "month": "2025-01", "total": 120000 },
{ "month": "2025-02", "total": 150000 },
{ "month": "2025-03", "total": 180000 }
]
}
4.1.3 top_products
Description: List the 5 best‑selling products by quantity sold.
Use cases: Identify popular items, stock prioritization, marketing focus.
Parameters: Same as analytics_summary.
Example response:
{
"success": true,
"products": [
{ "item_name": "Bag of Pure Water", "total_qty": 45, "total_revenue": 45000 },
{ "item_name": "Cement", "total_qty": 30, "total_revenue": 30000 }
]
}
4.1.4 payment_methods
Description: Breakdown of sales by payment method (Cash, Transfer, POS, Debt).
Use cases: Understand customer payment preferences, cash flow planning.
Parameters: Same as analytics_summary.
Example response:
{
"success": true,
"methods": [
{ "payment_method": "Cash", "count": 12, "total": 300000 },
{ "payment_method": "Transfer", "count": 8, "total": 250000 },
{ "payment_method": "Debt", "count": 5, "total": 150000 }
]
}
4.1.5 get_insights
Description: Return a list of business insights (e.g., best seller, low stock, top debtor) for the period.
Use cases: Proactive recommendations, alerts, decision support.
Parameters: Same as analytics_summary.
Example response:
{
"success": true,
"insights": [
{
"type": "success",
"title": "🔥 Best Seller",
"message": "Bag of Pure Water is your top seller with 45 units sold."
},
{
"type": "warning",
"title": "⚠️ Low Stock",
"message": "Items running low: Cement (3 left)"
}
]
}
4.1.6 customer_metrics
Description: Top 10 customers by total spent (within the date range). Includes order count, average order, and last purchase date.
Use cases: Loyalty programs, personalized offers, credit limit assessment.
Parameters: Same as analytics_summary.
Example response:
{
"success": true,
"customers": [
{
"client_id": 1,
"client_name": "John Doe",
"client_phone": "08012345678",
"order_count": 5,
"total_spent": 150000,
"avg_order": 30000,
"last_purchase": "2025-03-15 10:30:00"
}
]
}
4.1.7 inventory_turnover
Description: Get turnover rates and days in stock for each product.
Use cases: Identify slow‑moving items, optimize inventory, reduce holding costs.
Parameters: Same as analytics_summary.
Example response:
{
"success": true,
"items": [
{
"item_id": 1,
"item_name": "Bag of Pure Water",
"current_stock": 20,
"sold": 10,
"turnover_rate": 33.33,
"days_in_stock": 60
}
]
}
4.1.8 sales_forecast
Description: Predict next month's sales based on the last 6 months of data using linear regression.
Use cases: Financial planning, staffing, procurement.
Parameters: None.
Example request: action=sales_forecast
Example response:
{
"success": true,
"months": [ ... ],
"forecast": 128000,
"last_month": 120000,
"trend": "up"
}
4.1.9 cash_flow
Description: Projected cash flow for the next 30 days based on average daily sales, expenses, and outstanding receivables/payables.
Use cases: Liquidity planning, investment timing.
Parameters: None.
Example response:
{
"success": true,
"avg_daily_sales": 8000,
"avg_daily_expenses": 3000,
"receivables": 50000,
"payables": 20000,
"projected_inflow_30d": 265000,
"projected_outflow_30d": 110000,
"net_cash_flow": 155000
}
4.1.10 profit_margin
Description: Show profit margins per product (requires cost_price column to exist).
Use cases: Pricing strategy, cost reduction, product mix optimization.
Parameters: None.
Example response:
{
"success": true,
"items": [
{
"item_id": 1,
"item_name": "Bag of Pure Water",
"unit_price": 800,
"cost_price": 500,
"gross_profit": 300,
"margin_percent": 37.50
}
]
}
Error: If the cost_price column does not exist, returns a message with the required ALTER statement.
4.2 Inventory Management
These endpoints manage stock items. They require the inventory.read scope for read operations and inventory.write for write operations.
4.2.1 inventory (alias get_inventory_json)
Description: List all inventory items (including variants).
Use cases: Stock overview, product catalog, POS integration.
HTTP Method: GET
Endpoint: api_core.php?action=inventory
Parameters: None.
Example request:
GET https://workstack.com.ng/api_core.php?action=inventory
Example response:
{
"success": true,
"items": [
{
"item_id": 1,
"item_name": "Bag of Pure Water",
"unit_price": 800,
"stock_qty": 50,
"low_stock_threshold": 5,
"item_internal_notes": "Popular item",
"parent_id": null,
"attributes": null,
"parent_name": null
},
{
"item_id": 2,
"item_name": "Bag of Pure Water (Colour:Red)",
"unit_price": 800,
"stock_qty": 10,
"parent_id": 1,
"attributes": { "Colour": "Red" }
}
]
}
4.2.2 search_items
Description: Search for inventory items or bundles by name.
Use cases: Quick product lookup, autocomplete in POS.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | yes | Search term (minimum 1 character). |
Example: action=search_items&query=water
Response:
{
"success": true,
"items": [
{
"item_id": 1,
"item_name": "Bag of Pure Water",
"unit_price": 800,
"parent_id": null,
"variant_count": 2,
"type": "item"
}
]
}
Note: type is "item" for regular items, "parent" for items with variants, and "bundle" for bundles.
4.2.3 add_item
Description: Create a new inventory item (or variant).
Use cases: Product onboarding, adding new SKUs.
HTTP Method: POST
Parameters (form‑encoded):
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | yes | Item name. |
price | decimal | yes | Unit price. |
stock | int | yes | Initial stock quantity. |
threshold | int | no | Low stock alert threshold (default 5). |
notes | string | no | Internal notes. |
parent_id | int | no | If this is a variant, the ID of the parent item. |
attributes | JSON string | no | Attributes for variants (e.g., {"Colour":"Red"}). |
supplier_id | int | no | Supplier ID for this item. |
Example POST: action=add_item&name=Product&price=1000&stock=50&threshold=5
Response: { "success": true, "item_id": 1 }
4.2.4 edit_item
Description: Update an existing inventory item.
Use cases: Price changes, stock adjustments, editing notes.
Parameters: Same as add_item, plus item_id.
Example: action=edit_item&item_id=1&name=New Name&price=900&stock=45
4.2.5 delete_item
Description: Delete an inventory item (and its variants, if cascading).
Use cases: Removing discontinued products.
Parameters: item_id
Example: action=delete_item&item_id=1
4.2.6 get_variants
Description: Get all variants of a parent item.
Use cases: Displaying options in POS, product configuration.
HTTP Method: POST
Parameters (POST body): parent_id
Example POST: action=get_variants&parent_id=1
Response: Array of variant items.
4.2.7 save_variant
Description: Create or update a single variant.
Use cases: Adding a new colour/size to an existing product.
Parameters:
variant_id(optional, for update)parent_idname,price,stock,threshold,notes,attributes(JSON)
Example: action=save_variant&parent_id=1&name=Red&price=900&stock=10&attributes={"Colour":"Red"}
4.2.8 delete_variant
Description: Delete a specific variant.
Parameters: variant_id
4.2.9 generate_variants
Description: Generate all possible combinations of attributes for a parent item (Cartesian product).
Use cases: Quickly create a full product family.
Parameters:
parent_idattributes– JSON array of attribute types and their values, e.g.:
[
{ "type": "Colour", "values": ["Red", "Blue"] },
{ "type": "Size", "values": ["S", "M", "L"] }
]
Example: action=generate_variants&parent_id=1&attributes=[{"type":"Colour","values":["Red","Blue"]}]
Response: { "success": true, "count": 6 }
4.2.10 get_item_details
Description: Get full details for a single inventory item (including variant list if parent).
Use cases: Product editing screen, detail view.
HTTP Method: POST
Parameters (POST body): item_id
Example POST: action=get_item_details&item_id=1
Response: Item object with item_id, name, price, stock, variants (array if parent).
4.2.11 get_item_variants
Description: Get all variant rows for a parent item.
Use cases: Variant management UI.
HTTP Method: POST
Parameters (POST body): parent_id
Example POST: action=get_item_variants&parent_id=1
Response: Array of variant items.
4.3 Sales & Transactions
These endpoints record sales, manage debts, and retrieve sales history. They require sales.read for read actions and sales.write for write actions.
4.3.1 record_sale
Description: Record a new sale (POS). Deducts stock and updates client balance if payment method is "Debt".
Use cases: Checkout, point‑of‑sale, invoice payment.
HTTP Method: POST
Parameters (form‑encoded):
| Parameter | Type | Required | Description |
|---|---|---|---|
client_id | int | no | Client ID (0 or omitted for walk‑in). |
payment_method | string | no | One of: Cash, Transfer, POS, Debt. Default: Cash. |
cart | JSON | yes | Array of items: [{"id":1, "qty":2, "price":1000}, ...]. |
Example POST:
action=record_sale&client_id=123&payment_method=Cash&cart=[{"id":1,"qty":2,"price":1000}]
Response: { "success": true, "sale_ids": [123, 124], "sale_id": 123 }. sale_id is the first ID when multiple items; use sale_ids array for all.
4.3.2 sales
Description: List sales for a given month.
Use cases: Sales history, monthly reports.
Parameters:
month(string, YYYY‑MM) – default current month.
Example: action=sales&month=2025-03
Response:
{
"success": true,
"sales": [
{
"sale_id": 1001,
"sale_date": "2025-03-15 10:30:00",
"client_name": "John Doe",
"payment_method": "Cash",
"total_amount": 2000
}
]
}
4.3.3 get_sale_details
Description: Get detailed information about a specific sale (including items).
Use cases: Receipt printing, detailed transaction view.
Parameters: sale_id
Example: action=get_sale_details&sale_id=1001
Response:
{
"success": true,
"sale": {
"sale_id": 1001,
"sale_date": "2025-03-15 10:30:00",
"client_name": "John Doe",
"payment_method": "Cash",
"total_amount": 2000,
"items": [
{ "item_name": "Product A", "qty_sold": 2, "unit_price": 1000, "line_total": 2000 }
]
}
}
4.3.4 clear_debt
Description: Record a debt payment (reduces client's current balance).
Use cases: Receiving payments from customers on credit.
Parameters:
debt_id(string) – the debt ID returned fromrecord_salewhen payment is "Debt".amount(float) – amount being paid.
Example: action=clear_debt&debt_id=DEBT-20250315-123456&amount=5000
Note: The legacy client_id + sale_amount parameters are deprecated. Use debt_id + amount instead.
4.3.5 void_sale
Description: Cancel a sale. Restores stock and reverses any debt adjustment.
Use cases: Correcting erroneous entries, refunds.
Parameters: sale_id
Example: action=void_sale&sale_id=1001
4.4 Customer Management
These endpoints manage clients (customer ledger). They require clients.read for read actions and clients.write for write actions.
4.4.1 get_clients_json
Description: List all clients with their current balance.
Use cases: Customer directory, debtors list.
Example: action=get_clients_json
Response:
{
"success": true,
"clients": [
{
"client_id": 1,
"client_name": "John Doe",
"client_phone": "08012345678",
"current_balance": 5000,
"client_notes": "Regular customer"
}
]
}
4.4.2 get_client_by_id
Description: Get a single client by ID.
Use cases: Editing client details, showing profile.
Parameters: client_id
Example: action=get_client_by_id&client_id=1
4.4.3 add_client
Description: Create a new client.
Use cases: Adding a customer at checkout.
HTTP Method: POST
Parameters (POST body):
name(string, required)phone(string)email(string)balance(float) – initial balance (debt).notes(string)
Response: { "success": true, "client_id": 456 }
4.4.4 edit_client
Description: Update client details.
Parameters: client_id, name, phone, email, balance, notes.
4.4.5 delete_client
Description: Delete a client. Only allowed if client has no sales.
Use cases: Removing duplicate or test entries.
Parameters: client_id
4.4.6 get_client_debts
Description: Get all unpaid debts for a specific client.
Parameters: client_id
Example response:
{
"success": true,
"debts": [
{
"debt_id": "DEBT-20250315-123456",
"total_amount": 5000,
"sale_date": "2025-03-15 10:30:00"
}
]
}
4.5 Invoices (via api_cloud.php)
These endpoints manage digital invoices. They require invoices.read and invoices.write scopes.
4.5.1 list_invoices
Description: List invoices with optional filtering.
Use cases: Invoice dashboard, accounting integration.
Parameters:
status(draft, sent, paid, cancelled)limit(int, default 20)offset(int, default 0)
Example: action=list_invoices&status=paid
Response: Array of invoice summaries.
4.5.2 get_invoice
Description: Get a single invoice with all details.
Use cases: Displaying invoice, printing.
Parameters: invoice_id
4.5.3 save_invoice_draft
Description: Create or update an invoice. All fields are sent as POST data.
Use cases: Drafting and finalising invoices.
Parameters: All invoice fields (client info, items, totals, dates, styling, etc.). See the full invoice structure in the database.
4.5.4 delete_invoice
Description: Delete an invoice.
Use cases: Removing drafts or cancelled invoices.
Parameters: invoice_id
4.6 Reports & Exports
These endpoints generate downloadable CSV/PDF reports. They require reports.read scope.
4.6.1 export_clients_csv
Description: Export all clients to CSV.
Use cases: Data backup, import to other systems.
Example: action=export_clients_csv
Response: CSV file download.
4.6.2 export_sales_csv
Description: Export all sales to CSV.
Use cases: Accounting software import, audit trail.
Example: action=export_sales_csv
4.6.3 export_inventory_csv
Description: Export inventory to CSV.
Use cases: Stocktaking, bulk updates.
4.6.4 sales_report_pdf
Description: Generate a PDF sales report for a date range.
Use cases: Monthly reports, client statements.
Parameters: start (YYYY‑MM‑DD), end (YYYY‑MM‑DD) via GET.
Example: action=sales_report_pdf&start=2025-03-01&end=2025-03-31
4.6.5 export_clients_pdf
Description: Generate and download a PDF report of all clients.
4.6.6 export_inventory_pdf
Description: Generate and download a PDF report of all inventory items.
4.6.7 export_full_data
Description: Export all business data (clients, inventory, sales, expenses, suppliers) as a ZIP file containing CSV files.
4.7 Expenses
These endpoints manage business expenses. They require expenses.read for read actions and expenses.write for write actions.
4.7.1 add_expense
Description: Record a new expense. When the category is Restock, stock quantity is automatically updated.
Use cases: Recording business costs, inventory restocking.
HTTP Method: POST
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | float | yes | Expense amount (must be > 0). |
category | string | no | Any category name. For restocking, use Restock. Default: General. |
description | string | no | Expense description. |
supplier_id | int | no | Supplier ID (if expense is related to a supplier). |
item_id | int | if category=Restock | Inventory item ID to restock. |
restock_qty | int | if category=Restock | Quantity to add to stock. |
Example (General): action=add_expense&amount=5000&category=Utilities&description=Electricity+bill
Example (Restock): action=add_expense&amount=20000&category=Restock&item_id=1&restock_qty=20&supplier_id=1
4.7.2 list_expenses
Description: List all expenses for the current business, ordered by date descending.
Example response:
{
"success": true,
"data": [
{
"expense_id": 1,
"amount": 5000,
"category": "Utilities",
"description": "Electricity bill",
"date": "2025-03-15 10:30:00"
}
]
}
4.7.3 edit_expense
Description: Update an existing expense record.
Parameters: expense_id (required), amount, category, description.
4.8 Bundles
Bundles are groups of inventory items sold together as a single product. They require bundles.read for read actions and bundles.write for write actions.
4.8.1 get_bundles
Description: List all bundles with item counts.
4.8.2 get_bundle_details
Description: Get a single bundle with its constituent items.
HTTP Method: POST
Parameters (POST body): bundle_id
4.8.3 get_bundle_items
Description: Get items within a bundle with stock quantities (useful for POS).
HTTP Method: POST
Parameters (POST body): bundle_id
4.8.4 save_bundle
Description: Create or update a bundle.
HTTP Method: POST
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
bundle_id | int | no | Omit to create new bundle, include to update. |
name | string | yes | Bundle name. |
description | string | no | Bundle description. |
price | decimal | yes | Bundle selling price (must be > 0). |
items | JSON | yes | Array of {"item_id": int, "quantity": int}. |
4.8.5 delete_bundle
Description: Delete a bundle.
Parameters: bundle_id
4.9 Suppliers
These endpoints manage suppliers/vendors. They require suppliers.read for read actions and suppliers.write for write actions.
4.9.1 list_suppliers
Description: List all suppliers with full details.
4.9.2 list_suppliers_for_select
Description: List suppliers as id/name pairs (for dropdown selectors).
4.9.3 add_supplier
Description: Create a new supplier.
Parameters: name (required), contact_person, phone, email, address, notes.
4.9.4 edit_supplier
Description: Update supplier details.
Parameters: supplier_id, name, contact_person, phone, email, address, notes.
4.9.5 delete_supplier
Description: Delete a supplier.
Parameters: supplier_id
4.10 Delivery Management
These endpoints manage deliveries with customer/driver tracking codes and verification. They require deliveries.read for read actions and deliveries.write for write actions.
4.10.1 create_delivery
Description: Create a new delivery with auto-generated tracking codes.
HTTP Method: POST
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
sale_id | int | no | Associated sale ID. |
waybill_number | string | no | Custom waybill number (auto-generated if omitted). |
customer_name | string | yes | Recipient name. |
customer_phone | string | yes | Recipient phone. |
customer_address | string | yes | Delivery address. |
items | JSON | yes | Array of delivery items. |
carrier_type | string | no | standard or personal. Default: standard. |
carrier_id | int | no | Carrier or rider ID. |
4.10.2 update_delivery
Description: Update delivery fields (partial update — only sent fields are changed).
Parameters: delivery_id + any updatable fields (customer, carrier, etc.).
4.10.3 update_delivery_status
Description: Update the delivery status. Does NOT support delivered — use complete_delivery_with_code for that.
Allowed values: assigned, picked_up, in_transit, failed, returned
Parameters: delivery_id, status, note (optional), proof_image (optional, base64).
4.10.4 list_deliveries
Description: List deliveries with carrier names, paginated.
Parameters: limit (default 50), offset (default 0).
4.10.5 get_delivery
Description: Get a single delivery by ID (business owner view).
Parameters: delivery_id
4.10.6 get_delivery_by_code
Description: Get delivery details using a tracking code (public — no auth required). Used by customers and drivers to track deliveries.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
code | string | yes | Customer or driver tracking code. |
type | string | no | customer or driver. Default: customer. |
Note: Customer view hides driver tracking code and proof images. Driver view hides the verification code.
4.10.7 complete_delivery_with_code
Description: Mark a delivery as delivered using the verification code. This is the only way to set status to delivered.
Parameters: delivery_id, code (6-digit verification code).
4.10.8 return_delivery
Description: Mark a delivery as returned (allows changing from any status except returned or failed).
Parameters: delivery_id
4.10.9 delete_delivery
Description: Delete a delivery record.
Parameters: delivery_id
4.11 Carriers & Riders
Manage third-party carriers and personal riders for deliveries. These require deliveries.write scope.
4.11.1 add_carrier
Description: Register a third-party carrier company.
Parameters: name (required), website, tracking_url_template, phone.
4.11.2 list_carriers
Description: List active carriers.
4.11.3 delete_carrier
Description: Soft-delete a carrier (sets is_active = 0).
Parameters: carrier_id
4.11.4 add_rider
Description: Add a personal rider.
Parameters: name (required), phone (required), email, vehicle_type, license_plate, id_number.
4.11.5 list_riders
Description: List active personal riders.
4.11.6 delete_rider
Description: Soft-delete a rider (sets is_active = 0).
Parameters: rider_id
4.12 Imports
Import clients and inventory items from CSV or JSON data. Client imports require clients.write scope. Inventory imports require inventory.write scope.
4.12.1 import_clients
Description: Import clients from a CSV file. Premium feature.
HTTP Method: POST (multipart/form-data)
Parameters: file — CSV file with headers: name, phone, balance, notes. Max 2MB.
4.12.2 import_inventory
Description: Import inventory items from a CSV file. Premium feature.
Parameters: file — CSV file with headers: name, price, stock, threshold, notes. Max 2MB.
4.12.3 import_data
Description: Import data via JSON (not file upload). Supports clients and inventory types.
Parameters: type (clients or inventory), data (JSON array of objects). Max payload 2MB.
4.12.4 download_client_template
Description: Download a CSV template for client imports.
4.12.5 download_product_template
Description: Download a CSV template for inventory imports.
4.13 Utilities
4.13.1 check_token
Description: Validate an OAuth access token and return the associated user ID.
Use cases: Token health checks, debugging OAuth flows.
Authentication: Bearer token in Authorization header.
Example response:
{
"success": true,
"user_id": 42,
"expires": "2025-03-23 10:30:00"
}
4.13.2 logs
Description: Retrieve activity logs for the authenticated user. Supports filtering and pagination.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
filter | string | no | One of: SALE, INVENTORY, LEDGER, EXPENSE |
page | int | no | Page number (default 1). |
limit | int | no | Items per page, max 100 (default 50). |
4.13.3 set_onboarding_completed
Description: Mark the onboarding tutorial as completed for the current user.
5. Error Handling
5.1 HTTP Status Codes
| Code | Meaning |
|---|---|
| 200 | OK |
| 400 | Bad request (invalid parameters) |
| 401 | Unauthorized (missing or invalid token) |
| 403 | Forbidden (insufficient scope or premium required) |
| 404 | Resource not found |
| 500 | Internal server error |
5.2 API Error Response
{
"success": false,
"message": "Detailed error description"
}
5.3 Common Errors
| Error Message | Likely Cause |
|---|---|
Not authenticated | Missing or invalid token, or token not passed correctly. |
User has no active business | The user has a token but no active business membership. |
Invalid or expired token | Token is not in the database or has expired. |
Premium feature required | User does not have an active premium subscription. |
Insufficient stock for item | Attempted to sell more than available stock. |
6. Security Best Practices
- Always use HTTPS – never send tokens over plain HTTP.
- Store tokens securely – use environment variables, a secure database, and encrypt refresh tokens.
- Validate the
stateparameter – prevents CSRF attacks. - Limit scope – request only the permissions your app needs.
- Rotate secrets – periodically regenerate client secrets.
- Refresh tokens properly – use a secure store and handle token rotation.
7. Code Examples
7.1 PHP (cURL)
// Step 1: Redirect user
$state = bin2hex(random_bytes(16));
$_SESSION['oauth_state'] = $state;
$authUrl = 'https://workstack.com.ng/oauth/authorize.php?' . http_build_query([
'response_type' => 'code',
'client_id' => 'rikizy_prod',
'redirect_uri' => 'https://rikizy.com/oauth/callback',
'scope' => 'profile.read sales.read inventory.read',
'state' => $state
]);
header('Location: ' . $authUrl);
exit;
// Step 2: Callback handler
if ($_GET['state'] !== $_SESSION['oauth_state']) die('Invalid state');
$code = $_GET['code'];
$tokenUrl = 'https://workstack.com.ng/oauth/token.php';
$postData = [
'grant_type' => 'authorization_code',
'client_id' => 'rikizy_prod',
'client_secret' => YOUR_CLIENT_SECRET,
'code' => $code,
'redirect_uri' => 'https://rikizy.com/oauth/callback'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $tokenUrl);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
curl_close($ch);
$tokenData = json_decode($response, true);
$accessToken = $tokenData['access_token'];
// Step 3: Call API
$apiUrl = 'https://workstack.com.ng/api_core.php?action=inventory';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $apiUrl);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . $accessToken]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$apiResponse = curl_exec($ch);
curl_close($ch);
$data = json_decode($apiResponse, true);
print_r($data);
7.2 Python (requests)
import requests
import secrets
# Redirect user
state = secrets.token_hex(16)
auth_url = "https://workstack.com.ng/oauth/authorize.php"
params = {
"response_type": "code",
"client_id": "rikizy_prod",
"redirect_uri": "https://rikizy.com/oauth/callback",
"scope": "profile.read sales.read inventory.read",
"state": state
}
# ... redirect the user to auth_url ...
# Callback
if request.GET.get('state') != session_state: raise Exception("Invalid state")
code = request.GET.get('code')
# Exchange code
token_url = "https://workstack.com.ng/oauth/token.php"
data = {
"grant_type": "authorization_code",
"client_id": "rikizy_prod",
"client_secret": YOUR_CLIENT_SECRET,
"code": code,
"redirect_uri": "https://rikizy.com/oauth/callback"
}
resp = requests.post(token_url, data=data)
token_data = resp.json()
access_token = token_data["access_token"]
# Call API
api_url = "https://workstack.com.ng/api_core.php"
headers = {"Authorization": f"Bearer {access_token}"}
params = {"action": "inventory"}
api_resp = requests.get(api_url, headers=headers, params=params)
print(api_resp.json())
7.3 Node.js (Express)
const axios = require('axios');
const crypto = require('crypto');
app.get('/connect', (req, res) => {
const state = crypto.randomBytes(16).toString('hex');
req.session.oauthState = state;
const authUrl = `https://workstack.com.ng/oauth/authorize.php?${new URLSearchParams({
response_type: 'code',
client_id: 'rikizy_prod',
redirect_uri: 'https://rikizy.com/oauth/callback',
scope: 'profile.read sales.read inventory.read',
state
})}`;
res.redirect(authUrl);
});
app.get('/oauth/callback', async (req, res) => {
const { code, state } = req.query;
if (state !== req.session.oauthState) return res.status(400).send('Invalid state');
const tokenResponse = await axios.post('https://workstack.com.ng/oauth/token.php',
new URLSearchParams({
grant_type: 'authorization_code',
client_id: 'rikizy_prod',
client_secret: YOUR_CLIENT_SECRET,
code,
redirect_uri: 'https://rikizy.com/oauth/callback'
}),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
);
const { access_token } = tokenResponse.data;
const apiResponse = await axios.get('https://workstack.com.ng/api_core.php', {
params: { action: 'inventory' },
headers: { Authorization: `Bearer ${access_token}` }
});
console.log(apiResponse.data);
});
8. Frequently Asked Questions
Q: Do my users need a WorkStack account?
A: Yes. They must have a WorkStack account (free or premium) and be logged in to authorize.
Q: Can free users access all APIs?
A: Free users can access all non‑analytics endpoints. Analytics require a premium subscription.
Q: How long is an access token valid?
A: 24 hours. Use the refresh token to obtain a new one.
Q: What scopes should I request?
A: Only those your app actually needs. For example, if you only need to display sales data, request sales.read.
Q: Can I test locally?
A: Yes, you need to register a redirect URI that points to your local environment (e.g., http://localhost:3000/callback). WorkStack must be able to reach that URL (ngrok can help).
Q: How do I handle users without a WorkStack account?
A: Your app can redirect them to WorkStack's sign‑up page (https://workstack.com.ng/portal) before starting the OAuth flow.
Q: What if the user's premium expires?
A: Analytics endpoints will return a 403. Your app can detect this and prompt the user to upgrade.
Q: Can I check if a user has premium without accessing their data?
A: Yes. Request the profile.read scope and call analytics_summary. A 403 indicates they are not premium.
9. Support
For questions, issues, or to register your application:
- Email: support@workstack.com.ng
- Website: https://workstack.com.ng
Documentation version 2.0 – last updated July 2026