Authentication
All endpoints except / and /api/health require a Bearer token.
Authorization: Bearer <API_KEY>
Set via: wrangler secret put API_KEY · Returns 401 with hint if missing/invalid
Common Response Headers
| Header | Value |
| Content-Type | application/json |
| X-Content-Type-Options | nosniff |
| X-Frame-Options | DENY |
| X-XSS-Protection | 1; mode=block |
| Referrer-Policy | strict-origin-when-cross-origin |
| Content-Security-Policy | default-src 'none'; frame-ancestors 'none' |
| Access-Control-Allow-Origin | (matched origin or primary) |
Quick Start
Example Requests
# Health check (no auth)
curl -s https://gfs-platform.mikelevine.workers.dev/api/health | python3 -m json.tool
# KPIs (auth required)
curl -s -H "Authorization: Bearer YOUR_API_KEY" \
https://gfs-platform.mikelevine.workers.dev/api/kpis
# Search customers
curl -s -H "Authorization: Bearer YOUR_API_KEY" \
"https://gfs-platform.mikelevine.workers.dev/api/customers?q=driscoll&limit=5"
# AR aging
curl -s -H "Authorization: Bearer YOUR_API_KEY" \
https://gfs-platform.mikelevine.workers.dev/api/ar/aging
# Items with milk allergen
curl -s -H "Authorization: Bearer YOUR_API_KEY" \
"https://gfs-platform.mikelevine.workers.dev/api/items?allergen=milk&limit=10"
Pagination: Most list endpoints accept limit (1-200, default 50) and offset (0-100000, default 0). Total count returned where available.
Types: All amounts are number (REAL). All dates are string in M/D/YYYY format. All IDs are integer. Null fields are omitted or null.
Public Endpoints
PUBLIC GET /
Root status page
{
"name": "GFS Platform API",
"domain": "ai-globalfoodsolutions.co",
"version": "1.0.0",
"status": "operational",
"auth": "Bearer token required for all endpoints except /api/health",
"endpoints": "/api/health"
}
PUBLIC GET /api/health
Database health check with row counts for 8 main tables
{
"status": "ok",
"database": "gfs-netsuite",
"counts": {
"customers": 283, "vendors": 484, "items": 1265,
"transactions": 102367, "invoice_lines": 28528,
"so_lines": 29098, "vb_lines": 21315, "pricing": 1264
},
"timestamp": "2026-05-19T13:33:03.674Z"
}
KPI & Briefing Endpoints
AUTH GET /api/kpis
Core KPIs — revenue, AR, AP, open SOs
{
"revenue_2026_ytd": 11272175.89,
"revenue_2025_total": 28435586.26,
"open_ar": { "amount": 2299955.6, "count": 216 },
"open_ap": { "amount": ..., "count": ... },
"open_so": { "amount": ..., "count": 90 },
"timestamp": "..."
}
AUTH GET /api/briefing
Combined morning briefing data — KPIs + top customers + this month's invoices
{
"revenue_2026_ytd": ...,
"open_ar": { "amount": ..., "count": ... },
"open_so": { "amount": ..., "count": ... },
"invoices_this_month": { "amount": ..., "count": ... },
"top_customers_2026": [ { "entity_name": "Driscoll Foods", "revenue": 4107918.47 }, ... ],
"timestamp": "..."
}
Entity Endpoints
AUTH GET /api/customers
| Param | Type | Default | Description |
q | string | — | Search by company name (LIKE %q%) |
limit | int | 50 | 1-200 |
offset | int | 0 | 0-100000 |
{ "items": [...], "total": 283, "limit": 50, "offset": 0 }
AUTH GET /api/customers/:id
Full customer record + contacts + pricing + recent sales orders (20)
{ "id": 475, "companyname": "Jkings", ..., "contacts": [...], "pricing": [...], "recent_orders": [...] }
AUTH GET /api/customers/:id/history
Customer transaction history by year + top items purchased
{ "customer_id": "475", "yearly": [{ "year": 2026, "type": "CustInvc", "txn_count": 47, "total": ... }], "top_items": [...] }
AUTH GET /api/customers/ranking
Top 50 customers by invoice revenue for a given year
| Param | Type | Default |
year | int | 2026 |
{ "year": 2026, "items": [{ "customer": "Driscoll Foods", "customer_id": 539, "inv_count": ..., "revenue": 4107918.47 }, ...] }
AUTH GET /api/vendors
Active vendor list with terms (limit param, default 50)
AUTH GET /api/vendors/spend
Top 50 vendors by bill spend with min/max/avg
| Param | Type | Default |
year | int | 2026 |
Item Endpoints
AUTH GET /api/items
Item list with brand/class. Filter by type and allergen.
| Param | Type | Default | Allowed Values |
type | string | — | InvtPart, Assembly, Kit, NonInvtPart |
allergen | string | — | milk, eggs, peanuts, soybeans, wheat, fish, tree_nuts, crustacean, celery, lupin, molluscs, mustard, sesame, sulphur_dioxide |
limit | int | 50 | 1-200 |
offset | int | 0 | 0-100000 |
AUTH GET /api/items/:id
Item detail + revenue by year + cost by year + top 10 customers
AUTH GET /api/items/performance
Top 50 items by invoice revenue with qty sold, avg price, customer count
| Param | Type | Default |
year | int | 2026 |
AUTH GET /api/items/:id/customers
Which customers buy this item — revenue and qty by year (2024+)
Transaction & Financial Endpoints
AUTH GET /api/transactions
Transaction list by type with optional year filter
| Param | Type | Default | Allowed |
type | string | CustInvc | 16 validated types |
year | int | — | 2018-2027 |
limit | int | 50 | 1-200 |
offset | int | 0 | 0-100000 |
AUTH GET /api/ar/aging
{ "items": [{ "customer": "...", "customer_id": ..., "inv_count": ..., "total_ar": ..., "current_bal": ..., "days_1_30": ..., "days_31_60": ..., "days_61_90": ..., "days_90_plus": ... }] }
AUTH GET /api/financials/summary
Revenue vs COGS vs collections by year (all years)
{ "revenue": [{ "year": 2026, "invoices": 712, "total": 11272175.89 }, ...], "cogs": [...], "collections": [...] }
AUTH GET /api/financials/monthly
Monthly breakdown by transaction type
| Param | Type | Default |
year | int | 2026 |
AUTH GET /api/revenue/trend
Monthly invoice revenue from 2024 onward — for trend charts
AUTH GET /api/gl/accounts
Chart of accounts, optionally filtered by type
| Param | Type | Default | Values |
type | string | — | Bank, AcctRec, AcctPay, Income, COGS, Expense, Equity, OthCurrAsset, OthCurrLiab, FixedAsset, OthIncome, OthExpense, NonPosting |
Search
AUTH GET /api/search
Cross-entity search — searches customers, vendors, and items simultaneously
| Param | Type | Required |
q | string | Yes (max 100 chars) |
{ "results": [{ "id": 475, "name": "Jkings", "type": "customer" }, { "id": 539, "name": "Driscoll Foods", "type": "customer" }, ...] }
Error Responses
| Status | Body | Cause |
| 401 | {"error":"Unauthorized","hint":"Provide Authorization: Bearer <API_KEY> header"} | Missing or invalid Bearer token |
| 400 | {"error":"Invalid item type"} | Type/allergen not in allowlist |
| 400 | {"error":"Missing q parameter"} | Search endpoint without query |
| 404 | {"error":"Not found"} | Record ID doesn't exist |
| 404 | {"error":"Not found","endpoints":[...]} | Unknown path — returns endpoint list |
| 405 | {"error":"Method not allowed"} | Non-GET request (except OPTIONS for CORS) |
| 500 | {"error":"Internal server error"} | Unhandled exception (logged server-side) |