16 KiB
Document API Documentation
Overview
The Document API allows authenticated users to generate documents asynchronously (PDF, Excel, etc.) from provided data. The system uses a queue-based architecture for reliable, scalable document generation.
Key Features:
- ✅ Non-blocking requests (returns immediately with jobId)
- ✅ Job status polling to track progress
- ✅ Automatic retries with exponential backoff
- ✅ Support for multiple document types and formats
- ✅ Concurrent document generation
- ✅ Case-insensitive document names with whitespace and punctuation normalization
Architecture
Client Request → Queue Job → Return jobId (202)
↓
Worker processes
↓
Client polls status
↓
Job complete → Download file
Endpoints
1. Generate Document
Endpoint: POST /api/document/generate
URL: http://localhost:3070/api/document/generate
Authentication: Required ✓
Authorization: Required - User must have one of the following roles:
adminmanagementteam_headuser
HTTP Status: 202 Accepted
Request Headers
Content-Type: application/json
Authorization: Bearer <JWT_TOKEN>
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
document |
string | Yes | Type of document (e.g., "INVOICE", "DISPATCHNOTE", "DELIVERYNOTE") |
documentType |
string | Yes | Output format (e.g., "pdf", "excel") |
documentData |
object | Yes | Data object for the document |
Response (202 Accepted)
{
"success": true,
"message": "Document generation started",
"jobId": "1"
}
Error Responses
400 Bad Request:
{
"success": false,
"message": "document, documentType, and documentData are required"
}
500 Server Error:
{
"success": false,
"message": "Error message describing the issue"
}
Supported Document Values
You can send the document name in any case. Spaces, underscores, and hyphens are ignored by the generator.
PRECOSTINVOICEDISPATCHNOTEDELIVERYNOTECUSTOMDOCUMENTCREDITNOTEPO
Sample Request Bodies
All examples below are for POST /api/document/generate with documentType: "pdf".
PreCost
{
"document": "PRECOST",
"documentType": "pdf",
"documentData": {
"vessel_name": "MV OCEAN STAR",
"supplyPort": "DUBAI",
"clientRfqNum": "RFQ-2026-001",
"omsRfqNum": "OMS-RFQ-0001",
"date": "08/07/2026",
"items": [
{
"name": "Marine Rope",
"description": "High strength rope",
"qty": 10,
"unitPrice": 12.5
},
{
"name": "Safety Gloves",
"description": "Blue gloves",
"qty": 5,
"unitPrice": 8
}
],
"sub_total": 125,
"discount": 0,
"additionalCharges": 0,
"total_cost": 125
}
}
Invoice
{
"document": "INVOICE",
"documentType": "pdf",
"documentData": {
"jobReference": "JOB-2026-001",
"poNumber": "PO-20254161",
"date": "08/07/2026",
"pageLabel": "Page 01 of 02",
"billToName": "SUNRICH SHIP CHANDLERS L.L.C",
"billToAddress": "BUSINESS BAY - ASPECT TOWER, 22ND FLOOR, OFFICE NO. 2201, P.O. BOX NO 39976, DUBAI",
"vesselName": "MV OCEAN STAR",
"purposeOfCall": "SUPPLY OF PROVISIONS",
"supplyDate": "08/07/2026",
"grt": "12345",
"imoNumber": "9876543",
"portCountry": "DUBAI / UAE",
"items": [
{
"description": "Marine rope",
"remarks": "Delivered as requested",
"quantity": 10,
"unit_price": 12.5
},
{
"description": "Safety gloves",
"remarks": "Blue color",
"quantity": 5,
"unit_price": 8
}
],
"subtotal": 165,
"tax": 0,
"discount": 0,
"total": 165,
"paymentTerms": "Payment due within 30 days",
"notes": "Please verify quantities upon delivery."
}
}
Dispatch Note
{
"document": "DISPATCHNOTE",
"documentType": "pdf",
"documentData": {
"referenceNumber": "DN/2026/001",
"date": "08/07/2026",
"vessel": "MV OCEAN STAR",
"captain": "CAPT. JOHN DOE",
"cook": "SAMUEL",
"agent": "OCEANIC AGENT LTD",
"details": "Delivery of provisions and stores",
"placeOfDelivery": "DUBAI PORT",
"eta": "08/07/2026 08:00",
"etd": "08/07/2026 18:00",
"numberOfCrew": 18,
"nextMainOrderIn": "24 HRS",
"port": "DUBAI",
"company": "OCEANIC SHIP CHANDLERS (PVT) LTD",
"fileNo": "FILE-2026-04",
"items": [
{
"product": "Rice",
"qty": 10,
"unit": "BAGS",
"supp": "OMS",
"poNo": "PO-001",
"receivedQty": 10,
"issuedQty": 10,
"expDate": "12/07/2026",
"receivedTime": "09:15",
"date": "08/07/2026",
"rejects": "",
"fatCon": "",
"remark": "Delivered"
}
],
"showSignatures": true,
"preparedBy": "Admin User",
"approvedBy": "Manager User"
}
}
Delivery Note
{
"document": "DELIVERYNOTE",
"documentType": "pdf",
"documentData": {
"referenceNumber": "DN/OSC/2602/01",
"date": "08/07/2026",
"billToName": "SUNRICH SHIP CHANDLERS L.L.C",
"billToAddress": "BUSINESS BAY - ASPECT TOWER, 22ND FLOOR, OFFICE NO. 2201, P.O. BOX NO 39976, DUBAI",
"supplyDate": "08/07/2026",
"poNumber": "PO-20254161",
"items": [
{
"description": "Marine rope",
"remarks": "Delivered as requested",
"unit": "PCS",
"quantity": 10
},
{
"description": "Safety gloves",
"remarks": "Blue color",
"unit": "BOX",
"quantity": 5
}
]
}
}
Custom Document
{
"document": "CUSTOMDOCUMENT",
"documentType": "pdf",
"documentData": {
"title": "CUSTOM DOCUMENT",
"date": "08/07/2026",
"telePhone": "+94 112 083 206",
"emailAddress": "shipsupply@oceanicmsol.com",
"officeAddressLine1": "Level 5D, Valiant Towers, Nawala Mawatha",
"officeAddressLine2": "Colombo 02",
"officeAddressLine3": "SRI LANKA",
"directorOfCustoms": "The Director of Customs",
"vehicleNo": "WPLO 9767",
"permitNo": "MV20231214006001 / Permit No. MP20240717017006",
"chiefSecurityOfficer": "Chief Security Officer",
"exportGate": "Export Gate",
"slpa": "SLPA",
"gateNo": "Gate No. 03",
"permissionText": "Please grant permission to pass these Customs entry papers to supply goods to the vessel",
"companyName": "Oceanic Maritime Solutions (Pvt) Ltd",
"subtitle": "LICENSED SHIPCHANDLERS",
"sectionCode": "SC/FORM/06",
"items": [
{
"quantity": 10,
"unit": "PCS",
"description": "Marine rope",
"rate": 12.5,
"total": 125,
"confirmOrderRate": ""
},
{
"quantity": 5,
"unit": "PCS",
"description": "Safety gloves",
"rate": 8,
"total": 40,
"confirmOrderRate": ""
}
]
}
}
Credit Note
{
"document": "CREDITNOTE",
"documentType": "pdf",
"documentData": {
"companyName": "GREEK-LANKA MARITIME SERVICES (PVT) LTD",
"companyAddressLine1": "No. 56/2, Dharmapala Mw, Kotte",
"companyAddressLine2": "Sri Jayewardenepura",
"companyAddressLine3": "Sri Lanka",
"companyPostal": "Postal Code : 10100",
"companyContact": "Tel:+94 11 2083206 / Mobile (24/7) : +94 777 232 271",
"companyBR": "BR No : PV 0022630",
"companyLicense": "License No : SA00317-2024",
"crnNo": "GLMS/COLOMBO/474/CRN01",
"date": "26/07/2024",
"billToName": "Master & Owner of MV TEAM VENTURES",
"billToDetails": "VRIDHI MARITIME SHIP MANAGEMENT & OPERATION LLC\nOffice No: 2004, The Prism Tower, Dubai\n20th Floor, Business Bay,\nDubai - PO Box 29583.",
"vesselName": "MV \"TEAM VENTURES\"",
"grt": "25,543 MT",
"port": "REPAIRS AT COLOMBO DOCK YARD",
"imoNumber": "9339765",
"nameOfAgent": "29/04/2024 AT 21:00 HRS.LT",
"portCountry": "COLOMBO / SRI LANKA",
"items": [
{
"description": "SIGN OFF C/E MR. DHANSINGH BHAURYAL AND M/S J/E MR. SENTHIL",
"amount": 50
},
{
"description": "VISA + Handling Charges - M/S M/S MR. SENTHIL",
"amount": 100
},
{
"description": "Transport from Airport to Dockyard",
"amount": 70
}
],
"totalAmount": 220,
"approvedBy": "GLMS Finance Dept.",
"approvedByLabel": "Approved by GLMS Finance Dept.",
"departmentLabel": "GLMS - Accounts Department"
}
}
}
---
### 2. Get Job Status
**Endpoint:** `GET /api/document/job/:jobId/status`
**URL:** `http://localhost:3070/api/document/job/1/status`
**Authentication:** Required ✓
**Authorization:** Required
**HTTP Status:** `200 OK`
#### Request Headers
Authorization: Bearer <JWT_TOKEN>
#### Response (Waiting/Active)
```json
{
"success": true,
"jobId": "1",
"state": "waiting",
"result": null,
"error": null,
"attempts": 0,
"stacktrace": []
}
Response (Completed)
{
"success": true,
"jobId": "83",
"state": "completed",
"result": {
"fileName": "precost-1781614132061.pdf",
"mimeType": "application/pdf",
"size": 220008,
"s3Key": "uploads/16adf29b-c0c2-45f9-8808-d6e15cd3d755.pdf",
"documentId": "16adf29b-c0c2-45f9-8808-d6e15cd3d755"
},
"error": null,
"attempts": 1,
"stacktrace": []
}
Response (Failed)
{
"success": true,
"jobId": "1",
"state": "failed",
"result": null,
"error": "Error message explaining the failure",
"attempts": 3,
"stacktrace": ["stack trace line 1", "stack trace line 2"]
}
Job States
| State | Meaning | Next State |
|---|---|---|
waiting |
Job queued, waiting for worker | active |
active |
Worker currently processing | completed or failed |
completed |
Job done, result available | (final) |
failed |
Job failed after 3 retries | (final) |
delayed |
Scheduled for later processing | waiting |
Error Responses
404 Not Found:
{
"success": false,
"message": "Job not found"
}
3. Download Document
Endpoint: GET /api/document/download/:uuid
URL: http://localhost:3070/api/document/download/16adf29b-c0c2-45f9-8808-d6e15cd3d755
Authentication: Required ✓
Authorization: Required
HTTP Status: 200 OK
Request Headers
Authorization: Bearer <JWT_TOKEN>
Response (Success)
Returns the binary file with appropriate headers:
| Header | Value |
|---|---|
Content-Type |
application/pdf or application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
Content-Disposition |
attachment; filename="[fileName]" |
Response Body: Binary file content
Error Responses
400 Bad Request:
{
"success": false,
"message": "fileName is required"
}
404 Not Found:
{
"success": false,
"message": "Document not found"
}
4. Cancel Job
Endpoint: DELETE /api/document/job/:jobId
URL: http://localhost:3070/api/document/job/1
Authentication: Required ✓
Authorization: Required
HTTP Status: 200 OK
Request Headers
Authorization: Bearer <JWT_TOKEN>
Response (Success)
{
"success": true,
"message": "Job cancelled successfully",
"jobId": "1"
}
Error Responses
404 Not Found:
{
"success": false,
"message": "Job not found"
}
Document Data Notes
The generator is tolerant of different request shapes and common field aliases. For example, it accepts both documentData and nested data, and it normalizes document names by removing spaces, underscores, and hyphens.
If a document does not use every field in a sample payload, you can omit the unused keys.
Complete Workflow Example
1. Generate Document
curl -X POST http://localhost:3070/api/document/generate \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"document": "precost",
"documentType": "pdf",
"documentData": {
"customerName": "Acme Corp",
"items": [
{"name": "Item 1", "description": "Test", "qty": 2, "unitPrice": 100}
]
}
}'
Response:
{
"success": true,
"message": "Document generation started",
"jobId": "1"
}
2. Check Job Status
curl http://localhost:3070/api/document/job/1/status \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
Polling Response (Still Processing):
{
"success": true,
"jobId": "1",
"state": "active"
}
Polling Response (Completed):
{
"success": true,
"jobId": "1",
"state": "completed",
"result": {
"fileName": "precost-1715339340000.pdf"
}
}
3. Download File
curl http://localhost:3070/api/document/download/16adf29b-c0c2-45f9-8808-d6e15cd3d755 \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-o my-document.pdf
JavaScript/Fetch Example
async function generateAndDownloadDocument(token) {
// Step 1: Generate document
const generateRes = await fetch("/api/document/generate", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
document: "precost",
documentType: "pdf",
documentData: {
customerName: "Acme Corp",
items: [
{ name: "Item 1", description: "Test", qty: 2, unitPrice: 100 }
]
}
})
});
const { jobId } = await generateRes.json();
console.log("Job queued:", jobId);
// Step 2: Poll for completion
let isComplete = false;
let result = null;
while (!isComplete) {
const statusRes = await fetch(`/api/document/job/${jobId}/status`, {
headers: { "Authorization": `Bearer ${token}` }
});
const status = await statusRes.json();
console.log("Job state:", status.state);
if (status.state === "completed") {
result = status.result;
isComplete = true;
} else if (status.state === "failed") {
throw new Error(`Job failed: ${status.error}`);
} else {
// Wait 2 seconds before polling again
await new Promise(r => setTimeout(r, 2000));
}
}
// Step 3: Download the file
const downloadRes = await fetch(`/api/document/download/${result.fileName}`, {
headers: { "Authorization": `Bearer ${token}` }
});
const blob = await downloadRes.blob();
// Trigger browser download
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = result.fileName;
a.click();
window.URL.revokeObjectURL(url);
console.log("Download complete!");
}
Important Notes
Authentication & Authorization
- All endpoints require JWT token in
Authorization: Bearer <token>header - User must have one of these roles:
admin,management,team_head,user
Polling Strategy
- Poll status every 2-5 seconds
- Max recommended: 60 attempts (5 minutes timeout)
- Move to download immediately when state is
completed
Error Handling
- Jobs automatically retry up to 3 times with exponential backoff
- Failed jobs remain in queue for debugging
- Check
errorandstacktracefields for failure details
File Management
- Generated files stored in
/app/documents/ - File names auto-generated:
{document}-{timestamp}.{ext} - Always sanitize fileName in download requests
Supported Formats
- PDF:
documentType: "pdf" - Excel:
documentType: "excel"
Performance
- Worker concurrency: 2 concurrent jobs
- Timeout per job: 5 minutes
- Retry attempts: 3 with exponential backoff
Common Response Codes
| Code | Meaning |
|---|---|
202 |
Accepted - Job queued successfully |
200 |
OK - Successful operation |
400 |
Bad Request - Invalid input |
401 |
Unauthorized - Missing/invalid token |
403 |
Forbidden - Insufficient permissions |
404 |
Not Found - Job or file not found |
500 |
Server Error - Internal error |
Migration Notes
If upgrading from synchronous API:
Old (Synchronous):
- Request returned file immediately
- Long request timeout
- Blocking operation
New (Asynchronous):
- Request returns jobId immediately (202)
- Poll
/api/document/job/{jobId}/statusfor progress - Non-blocking, scalable architecture
- Automatic retries built-in