Order Creation SaaS API — A backend service for generating and managing standardized UBL 2.1 Order documents. Part of the SENG2021 project; designed for use by other teams as a shared API for the procurement process between Buyer and Seller parties.
- Overview
- Prerequisites
- Installation
- Configuration
- Running the service
- API overview
- Response format
- Project structure
- Testing
- Deployment
- Repository & support
Northbound provides:
- Authentication — User registration and login (JWT).
- Health check — Service status and uptime.
- Orders — Create orders, list them, retrieve UBL XML, replace an order (full payload) via
PUT /orders/:id/change(or/v1/orders/...), cancel viaPOST /orders/:id/cancel, update buyer/seller country viaPATCH /orders/:id/party-country, and manage recurring templates under/orders/recurring. - Invoicing + tax — Generate a UBL 2.1 Invoice from a stored order (with optional tax calculation) via
POST /orders/:id/invoice. - API documentation — Interactive Swagger UI at
/docs, backed by OpenAPI 3 (openapi.yaml). - Multi-language (frontend) — UI strings can be switched between languages and are saved locally in the browser.
- Multi-currency (frontend) — Orders store ISO 4217 currency codes; analytics can convert values into a base currency using live FX rates.
The API uses a standard JSON response envelope for success and error responses so integrating clients can handle them consistently.
- Node.js 18+ (LTS recommended)
- npm 9+
-
Clone the repository
git clone https://github.com/CongeeZee/Northbound.git cd Northbound -
Install dependencies
npm install
-
Configure environment — See Configuration.
Create a .env file in the project root. The following variables are used:
| Variable | Required | Description |
|---|---|---|
PORT |
No | Server port. Default: 3000. |
NODE_ENV |
No | Set to test when running tests. |
JWT_SECRET |
Yes (for auth) | Secret used to sign and verify JWT tokens. Use a long, random string in production. |
SUPABASE_URL |
Yes (for orders) | Your Supabase project URL. |
SUPABASE_ANON_KEY |
Yes (for orders) | Supabase anonymous (public) key for API access. |
DEVEX_API_KEY |
No | DevEx Despatch API key for /orders/.../despatch routes. |
DEVEX_API_BASE_URL |
No | Defaults to https://devex.cloud.tcore.network. |
Example .env (do not commit this file):
PORT=3000
JWT_SECRET=your-secret-key-min-32-chars-for-production
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-keyCopy from .env.example if present and fill in the values. Ensure .env is listed in .gitignore (it is by default).
-
Development (TypeScript via ts-node, or run built JS):
npm run build npm start
The server listens on
http://localhost:3000(or the port set inPORT). -
Production: Run the compiled JavaScript:
npm run build npm start
npm startrunsnode dist/server.js.
Base URL when running locally: http://localhost:3000. Deployed example: https://northbound-w6b3.onrender.com.
Most API routes require Authorization: Bearer <token> (all /orders/* and /parties/* routes). Auth routes under /auth/* are public.
| Method | Path | Description |
|---|---|---|
| GET | /docs |
Swagger UI — Interactive API documentation. |
| GET | /health |
Health check; returns status, uptime, and version. |
| POST | /auth/register |
Register a new user. Body: email, password, passwordConfirm. |
| POST | /auth/login |
Log in. Body: email, password. Returns userID and token. |
| GET | /orders or /v1/orders |
List all orders. |
| POST | /orders or /v1/orders/generate |
Create an order and generate UBL XML. Body: see Order request body below. |
| GET | /orders/:id or /v1/orders/:id |
Retrieve one order by ID (JSON). |
| GET | /orders/:id/xml or /v1/orders/:id/xml |
Retrieve the UBL Order XML for order id. Returns application/xml. |
| PATCH | /orders/:id/party-country or /v1/orders/:id/party-country |
Update buyer or seller country (ISO code); returns regenerated UBL XML. Requires Supabase. |
| PATCH | /orders/:id/detail or /v1/orders/:id/detail |
Patch header fields (currency, issue_date, order_note) on a non-recurring order; returns regenerated UBL Order XML. Requires Supabase. |
| POST | /orders/:id/response or /v1/orders/:id/response |
Body: response_code (required), optional issue_date, note. Returns UBL OrderResponse XML for an existing order (not stored). Requires Supabase. |
| PUT | /orders/:id/change or /v1/orders/:id/change |
Replace the order with a full new payload (same shape as create); same id; returns updated UBL XML. Requires Supabase. |
| POST | /orders/:id/cancel or /v1/orders/:id/cancel |
Cancel (delete) the order and its line items; returns orderID. Requires Supabase. |
| POST | /orders/:id/invoice or /v1/orders/:id/invoice |
Generate a UBL Invoice from a stored order. Body supports optional tax_rate, issue_date, invoice_note. Requires Supabase. |
| POST | /orders/recurring or /v1/orders/recurring |
Create a recurring order template. |
| PATCH | /orders/recurring/:id or /v1/orders/recurring/:id |
Partial update of a recurring order (optional fields include currency, schedule, parties, order_lines). |
| DELETE | /orders/recurring/:id or /v1/orders/recurring/:id |
Delete a recurring order template. |
| GET | /orders/despatch/list or /v1/orders/despatch/list |
DevEx integration: list Despatch Advice records for your API key (requires DEVEX_API_KEY). |
| POST | /orders/:id/despatch or /v1/orders/:id/despatch |
DevEx integration: send this order’s UBL Order XML to DevEx to create Despatch Advice (DEVEX_API_KEY). |
| GET | /orders/:id/despatch?adviceId=<uuid> |
DevEx integration: retrieve Despatch Advice (by adviceId, or omit for lookup by order XML). |
| POST | /auth/forgot-password |
Request a password reset (email in body). |
| POST | /auth/reset-password |
Complete reset with token and newPassword. |
| POST | /auth/logout |
Revoke JWT (Authorization: Bearer …). |
| GET | /parties/buyers/:externalID/orders |
(Auth) List all orders for a buyer (includes order lines). |
| GET | /parties/sellers/:externalID/orders |
(Auth) List all orders for a seller (includes order lines). |
| GET | /parties/buyers/:externalID/report |
(Auth) Aggregated buyer spend report (totals + currency breakdown + order summaries). |
| GET | /parties/sellers/:externalID/report |
(Auth) Aggregated seller revenue report (totals + currency breakdown + order summaries). |
For full request/response schemas and examples, use Swagger UI at GET /docs after starting the server (spec: OpenAPI 3.0).
The frontend includes a language switcher (see frontend/src/components/layout/LanguageSwitcher.tsx) backed by frontend/src/context/LanguageContext.tsx.
- Supported languages: English (
en), Spanish (es), French (fr), German (de), Chinese (zh). - Persistence: saved in
localStorageunder the keynorthbound_language. - Fallback behavior: missing keys fall back to English.
This is a frontend feature (no backend API required).
- API: Orders store
currencyas an ISO 4217 code (e.g.AUD,USD,EUR) on create/change and can be updated viaPATCH /orders/:id/detail. - Frontend default currency: saved locally via
frontend/src/hooks/usePreferences.tsundernorthbound_preferences(field:defaultCurrency). - FX conversion (analytics): the frontend can convert values into a base currency using live rates from
https://open.er-api.com/v6/latest/USD(cached for 1 hour) viafrontend/src/hooks/useExchangeRates.ts.
Tax is applied when generating invoices from stored orders:
- Endpoint:
POST /orders/:id/invoice(also under/v1/orders/:id/invoice) - Request body:
tax_rate(optional): decimal (e.g.0.1for 10%). If omitted, the service uses the seller’s country to choose a default tax rate.issue_date(optional):YYYY-MM-DD(defaults to today)invoice_note(optional): free text note
- Default tax lookup: country → rate/name mapping in
src/orders/tax.ts(returns{ rate: 0, name: "None" }for unknown/missing countries).
For full schemas and examples, see Swagger UI (/docs) → Orders → Generate UBL Invoice from an order.
Create-order and change-order requests use this JSON shape (e.g. POST /orders, POST /v1/orders/generate, or PUT /orders/{id}/change):
{
"buyer": {
"external_id": "BUYER-001",
"name": "Example Buyer Pty Ltd",
"email": "buyer@example.com",
"street": "123 Market St",
"city": "Sydney",
"country": "AU",
"postal_code": "2000"
},
"seller": {
"external_id": "SELLER-001",
"name": "Example Seller Pty Ltd"
},
"currency": "AUD",
"issue_date": "2025-03-10",
"order_note": "Optional note",
"order_lines": [
{
"line_id": "1",
"description": "Wireless Keyboard",
"quantity": 2,
"unit_price": 49.99,
"unit_code": "EA"
}
]
}- Required:
buyer,seller,currency,issue_date(YYYY-MM-DD),order_lines(non-empty array). Each party needsexternal_idandname. Each line needsline_id,description,quantity,unit_price. - Optional:
order_note; party fieldsemail,street,city,country,postal_code; line fieldunit_code(defaults toEA).
All JSON responses (except raw XML from /v1/orders/:id/xml) follow this envelope:
Success:
{
"success": true,
"message": "Human-readable message",
"data": { ... },
"error": null
}Error:
{
"success": false,
"message": "Short description",
"data": null,
"error": {
"code": "ERROR_CODE",
"message": "Detailed message",
"validationErrors": []
}
}validationErrorsis optional and used for validation failures (e.g. field-level errors).- Use
successto decide whether to readdataorerror. HTTP status codes still reflect the outcome (4xx client errors, 5xx server errors).
Northbound/
├── src/
│ ├── app.ts # Express app: middleware, routes, /docs
│ ├── server.ts # Entry point: starts HTTP server
│ ├── errors.ts # Standard response helpers (ok, fail, AppError)
│ ├── auth/ # Registration, login, JWT
│ ├── health/ # Health check route
│ ├── orders/ # Order CRUD, UBL generation, storage
│ └── validation/ # Order input validation
├── openapi.yaml # OpenAPI 3.0 spec for API docs (Swagger UI)
├── package.json
├── tsconfig.json
└── .env # Not committed; copy from .env.example
- Other teams: Use the same base URL (e.g. deployed
https://northbound-w6b3.onrender.comor localhttp://localhost:3000). Use/auth/loginor/auth/registerfor a token; sendAuthorization: Bearer <token>when required. UsePOST /ordersorPOST /v1/orders/generateto create orders; usePUT /orders/{orderID}/changeto replace an order (full body); usePOST /orders/{orderID}/cancelto cancel. See Order request body or Swagger UI at/docs.
-
Run all tests
npm testUses Jest; test files sit next to source (e.g.
*.test.ts). -
Test environment: Set
NODE_ENV=test(or the test runner does). Ensure.envor test setup provides any required env vars (e.g.JWT_SECRET, Supabase) for tests that hit the API or DB.
The API can be deployed to any Node-friendly host (e.g. Render, Vercel, Railway). Ensure the following are set in the environment:
PORT(if required by the platform)JWT_SECRET(required for auth)SUPABASE_URLandSUPABASE_ANON_KEY(required for order storage and retrieval)
Live example: https://northbound-w6b3.onrender.com — the root URL redirects to Swagger UI at /docs; health check at /health.
- Repository: github.com/CongeeZee/Northbound
- Issues: GitHub Issues
For integration questions or bugs, open an issue or contact the Northbound team.