Skip to main content

Form Submission API Documentation

This document outlines the API requirements for handling form submissions from the Forms Portal application.

Overview

The Forms Portal frontend allows users to browse, fill out, and submit company forms. When a user submits a form through the web interface, the data is sent to a Node.js backend API for processing, storage, and workflow management.

API Endpoints

Submit Form

Endpoint: POST /api/forms/submit

Purpose: Submit a completed form for processing

Request Headers:

Content-Type: application/json
Authorization: Bearer {jwt_token}

Request Body:

{
"formId": "employee-onboarding",
"title": "Employee Onboarding Form",
"submittedDate": "2025-07-26",
"data": {
"Name": "John Doe",
"Employee ID": "EMP12345",
"Department": "Engineering",
"Start Date": "2025-08-01",
"Manager": "Jane Smith"
}
}

Success Response:

{
"success": true,
"message": "Form submitted successfully",
"submissionId": "1234567890",
"status": "submitted"
}

Error Response:

{
"success": false,
"message": "Failed to submit form",
"error": "Required field 'Manager' is missing"
}

Get User Forms

Endpoint: GET /api/forms/user

Purpose: Retrieve all forms submitted by the current user

Request Headers:

Authorization: Bearer {jwt_token}

Success Response:

{
"success": true,
"forms": [
{
"id": "1234567890",
"formId": "employee-onboarding",
"title": "Employee Onboarding Form",
"submittedDate": "2025-07-26",
"status": "submitted",
"approver": "HR Manager",
"comments": ""
},
{
"id": "0987654321",
"formId": "expense-reimbursement",
"title": "Expense Reimbursement",
"submittedDate": "2025-07-20",
"status": "approved",
"approver": "Finance Manager",
"comments": "Approved on July 23, 2025"
}
]
}

Get Form Status

Endpoint: GET /api/forms/status/:submissionId

Purpose: Check the status of a submitted form

Request Headers:

Authorization: Bearer {jwt_token}

Success Response:

{
"success": true,
"submission": {
"id": "1234567890",
"formId": "employee-onboarding",
"title": "Employee Onboarding Form",
"submittedDate": "2025-07-26",
"status": "under-review",
"approver": "HR Manager",
"comments": "Waiting for department head approval",
"history": [
{
"date": "2025-07-26T14:30:00Z",
"action": "submitted",
"by": "John Doe"
},
{
"date": "2025-07-26T15:45:00Z",
"action": "under-review",
"by": "HR Manager"
}
]
}
}

Update Form Status

Endpoint: PUT /api/forms/status/:submissionId

Purpose: Update the status of a form (for approvers)

Request Headers:

Content-Type: application/json
Authorization: Bearer {jwt_token}

Request Body:

{
"status": "approved",
"comments": "All information is correct. Employee is ready to start.",
"nextApprover": "IT Department" // Optional, only if forwarding to another approver
}

Success Response:

{
"success": true,
"message": "Form status updated successfully",
"submission": {
"id": "1234567890",
"status": "approved",
"approver": "IT Department",
"comments": "All information is correct. Employee is ready to start."
}
}

Error Response:

{
"success": false,
"message": "Failed to update form status",
"error": "NOT_AUTHORIZED_APPROVER"
}

Get Pending Approvals

Endpoint: GET /api/forms/pending-approvals

Purpose: Retrieve all forms pending approval by the current user

Request Headers:

Authorization: Bearer {jwt_token}

Success Response:

{
"success": true,
"pendingApprovals": [
{
"id": "1234567890",
"formId": "employee-onboarding",
"title": "Employee Onboarding Form",
"submittedDate": "2025-07-26",
"submittedBy": "John Doe",
"status": "under-review",
"deadline": "2025-07-29" // Optional deadline for approval
},
{
"id": "0987654321",
"formId": "vacation-request",
"title": "Vacation Request Form",
"submittedDate": "2025-07-25",
"submittedBy": "Jane Smith",
"status": "under-review",
"deadline": "2025-07-28"
}
]
}

Server Implementation Requirements

Node.js Server

The server should be built using Node.js with Express.js for API routing. Here's a basic structure:

const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const jwt = require('jsonwebtoken');
const mongoose = require('mongoose');

// Initialize Express app
const app = express();

// Middleware
app.use(cors());
app.use(bodyParser.json());

// Authentication middleware
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];

if (!token) return res.status(401).json({ success: false, message: 'Authentication required' });

jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.status(403).json({ success: false, message: 'Invalid or expired token' });

req.user = user;
next();
});
};

// Form submission endpoint
app.post('/api/forms/submit', authenticateToken, async (req, res) => {
try {
const { formId, title, data } = req.body;

// Validate required fields based on form type
const formValidation = await validateFormFields(formId, data);
if (!formValidation.valid) {
return res.status(400).json({
success: false,
message: 'Validation failed',
error: formValidation.error
});
}

// Create new form submission
const submission = await FormSubmission.create({
formId,
title,
submittedDate: new Date(),
data,
userId: req.user.id,
userName: req.user.name,
status: 'submitted',
approvers: await getFormApprovers(formId),
currentApprover: await getFirstApprover(formId)
});

// Trigger notification to approvers
await notifyApprovers(submission);

res.status(201).json({
success: true,
message: 'Form submitted successfully',
submissionId: submission._id,
status: submission.status
});
} catch (error) {
console.error('Error submitting form:', error);
res.status(500).json({
success: false,
message: 'Failed to submit form',
error: 'An internal server error occurred'
});
}
});

// Get user's forms
app.get('/api/forms/user', authenticateToken, async (req, res) => {
try {
const submissions = await FormSubmission.find({ userId: req.user.id })
.sort({ submittedDate: -1 })
.select('-data'); // Exclude form data for the list view

res.json({
success: true,
forms: submissions.map(sub => ({
id: sub._id,
formId: sub.formId,
title: sub.title,
submittedDate: sub.submittedDate,
status: sub.status,
approver: sub.currentApprover,
comments: sub.comments
}))
});
} catch (error) {
console.error('Error fetching user forms:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch forms',
error: 'An internal server error occurred'
});
}
});

// Get form status
app.get('/api/forms/status/:submissionId', authenticateToken, async (req, res) => {
try {
const { submissionId } = req.params;

// Find the submission
const submission = await FormSubmission.findById(submissionId);

// Check if submission exists
if (!submission) {
return res.status(404).json({
success: false,
message: 'Form submission not found',
error: 'SUBMISSION_NOT_FOUND'
});
}

// Check if user is authorized to view this submission
if (submission.userId !== req.user.id && !await isUserApprover(req.user.id, submission)) {
return res.status(403).json({
success: false,
message: 'Not authorized to view this submission',
error: 'NOT_AUTHORIZED'
});
}

res.json({
success: true,
submission: {
id: submission._id,
formId: submission.formId,
title: submission.title,
submittedDate: submission.submittedDate,
status: submission.status,
approver: submission.currentApprover,
comments: submission.comments,
history: submission.history
}
});
} catch (error) {
console.error('Error fetching form status:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch form status',
error: 'An internal server error occurred'
});
}
});

// Update form status (for approvers)
app.put('/api/forms/status/:submissionId', authenticateToken, async (req, res) => {
try {
const { submissionId } = req.params;
const { status, comments, nextApprover } = req.body;

// Find the submission
const submission = await FormSubmission.findById(submissionId);

// Check if submission exists
if (!submission) {
return res.status(404).json({
success: false,
message: 'Form submission not found',
error: 'SUBMISSION_NOT_FOUND'
});
}

// Check if user is authorized to update this submission (must be current approver)
if (!await isUserCurrentApprover(req.user.id, submission)) {
return res.status(403).json({
success: false,
message: 'Not authorized to update this submission',
error: 'NOT_AUTHORIZED_APPROVER'
});
}

// Update the submission status
submission.status = status;
submission.comments = comments || submission.comments;

// Add to history
submission.history.push({
date: new Date(),
action: status,
by: req.user.name,
comments: comments || ''
});

// If this is an approval and there's a next approver, update currentApprover
if (status === 'approved' && nextApprover) {
submission.currentApprover = nextApprover;

// Send notification to next approver
await notifyNextApprover(submission, nextApprover);
} else if (status === 'approved' && !nextApprover) {
// This is the final approval
submission.finalApprovalDate = new Date();

// Notify the form submitter
await notifySubmitterOfApproval(submission);
} else if (status === 'rejected') {
// Notify the submitter of rejection
await notifySubmitterOfRejection(submission, comments);
}

// Save the updated submission
await submission.save();

res.json({
success: true,
message: 'Form status updated successfully',
submission: {
id: submission._id,
status: submission.status,
approver: submission.currentApprover,
comments: submission.comments
}
});
} catch (error) {
console.error('Error updating form status:', error);
res.status(500).json({
success: false,
message: 'Failed to update form status',
error: 'An internal server error occurred'
});
}
});

// Get pending approvals for current user
app.get('/api/forms/pending-approvals', authenticateToken, async (req, res) => {
try {
// Find submissions where current user is the approver
const pendingApprovals = await FormSubmission.find({
currentApprover: req.user.name,
status: { $in: ['submitted', 'under-review'] }
}).select('-data').sort({ submittedDate: 1 });

res.json({
success: true,
pendingApprovals: pendingApprovals.map(submission => ({
id: submission._id,
formId: submission.formId,
title: submission.title,
submittedDate: submission.submittedDate,
submittedBy: submission.userName,
status: submission.status,
deadline: calculateDeadline(submission.submittedDate, submission.formId)
}))
});
} catch (error) {
console.error('Error fetching pending approvals:', error);
res.status(500).json({
success: false,
message: 'Failed to fetch pending approvals',
error: 'An internal server error occurred'
});
}
});

// Start server
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});

Database Schema

Using MongoDB with Mongoose, the form submission schema should look like:

const mongoose = require('mongoose');

const formSubmissionSchema = new mongoose.Schema({
formId: {
type: String,
required: true
},
title: {
type: String,
required: true
},
submittedDate: {
type: Date,
default: Date.now
},
data: {
type: Object,
required: true
},
userId: {
type: String,
required: true
},
userName: {
type: String,
required: true
},
status: {
type: String,
enum: ['draft', 'submitted', 'under-review', 'approved', 'rejected'],
default: 'submitted'
},
approvers: [{
role: String,
name: String,
email: String,
order: Number
}],
currentApprover: {
type: String
},
comments: {
type: String,
default: ''
},
history: [{
date: Date,
action: String,
by: String,
comments: String
}]
});

const FormSubmission = mongoose.model('FormSubmission', formSubmissionSchema);

module.exports = FormSubmission;

Integration with Workflow Systems

The form submission API should be designed to integrate with workflow systems for approvals and notifications:

  1. Email Notifications: Send emails to approvers when a form requires their review
  2. Status Updates: Update form status as it progresses through the approval workflow
  3. Approval Chains: Support sequential approval chains where forms must be approved in a specific order

Security Considerations

  1. Authentication: Use JWT tokens for authenticating API requests
  2. Authorization: Ensure users can only access their own form submissions
  3. Input Validation: Validate all form fields before processing submissions
  4. Audit Logging: Maintain comprehensive logs of all form activities
  5. Data Encryption: Encrypt sensitive form data both in transit and at rest

API Versioning Strategy

To maintain backward compatibility while allowing for future enhancements, the API should implement a proper versioning strategy:

  1. URL Path Versioning: Use URL paths like /api/v1/forms/submit to clearly indicate the API version.
  2. Documentation Versioning: Maintain separate documentation for each API version.
  3. Deprecation Process: Clearly communicate deprecation timelines:
    • Announce deprecation at least 6 months before end-of-life
    • Provide migration guides to newer versions
    • Return deprecation warnings in headers for deprecated endpoints

Example of version management in Express.js:

// Version 1 router
const v1Router = express.Router();
app.use('/api/v1', v1Router);

// Version 2 router
const v2Router = express.Router();
app.use('/api/v2', v2Router);

// Version 1 endpoint
v1Router.post('/forms/submit', authenticateToken, legacyFormSubmitHandler);

// Version 2 endpoint with enhanced functionality
v2Router.post('/forms/submit', authenticateToken, enhancedFormSubmitHandler);

Error Handling and Rate Limiting

Error Handling

The API should implement standardized error responses:

  1. HTTP Status Codes: Use appropriate status codes (400, 401, 403, 404, 500, etc.)
  2. Error Object Structure: Consistent structure for all error responses:
    {
    "success": false,
    "message": "Human-readable error message",
    "error": "ERROR_CODE",
    "details": {
    "field": "field_with_error",
    "reason": "Detailed explanation"
    }
    }
  3. Logging: Log errors with correlation IDs for debugging

Rate Limiting

Implement rate limiting to prevent abuse:

const rateLimit = require('express-rate-limit');

// General rate limiting
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: {
success: false,
message: 'Too many requests, please try again later',
error: 'RATE_LIMIT_EXCEEDED'
}
});

// Apply to all routes
app.use('/api/', apiLimiter);

// More strict limiting for form submission
const formSubmitLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 10, // limit each IP to 10 form submissions per hour
message: {
success: false,
message: 'Too many form submissions, please try again later',
error: 'FORM_SUBMISSION_LIMIT_EXCEEDED'
}
});

// Apply to form submission endpoint
app.use('/api/forms/submit', formSubmitLimiter);

Client SDK

To facilitate integration with the Forms API, a JavaScript client SDK should be provided:

// forms-api-client.js
class FormsApiClient {
constructor(baseUrl, authToken) {
this.baseUrl = baseUrl || 'https://api.company.com';
this.authToken = authToken;
}

setAuthToken(token) {
this.authToken = token;
}

async submitForm(formData) {
return this._request('POST', '/api/forms/submit', formData);
}

async getUserForms() {
return this._request('GET', '/api/forms/user');
}

async getFormStatus(submissionId) {
return this._request('GET', `/api/forms/status/${submissionId}`);
}

async updateFormStatus(submissionId, statusUpdate) {
return this._request('PUT', `/api/forms/status/${submissionId}`, statusUpdate);
}

async _request(method, endpoint, data = null) {
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.authToken}`
};

const options = {
method,
headers,
credentials: 'include'
};

if (data && (method === 'POST' || method === 'PUT')) {
options.body = JSON.stringify(data);
}

try {
const response = await fetch(`${this.baseUrl}${endpoint}`, options);
const responseData = await response.json();

if (!response.ok) {
throw new Error(responseData.message || 'API request failed');
}

return responseData;
} catch (error) {
console.error('API request error:', error);
throw error;
}
}
}

// Usage example:
// const client = new FormsApiClient();
// client.setAuthToken(userJwtToken);
// const result = await client.submitForm({ formId: 'employee-onboarding', ... });

Testing the API

The API should be thoroughly tested using:

  1. Unit Tests: Test individual functions and components
  2. Integration Tests: Test the entire form submission flow
  3. Load Tests: Ensure the API can handle multiple concurrent submissions

Deployment

The Node.js server should be deployed in a containerized environment (Docker) to ensure consistency across development, staging, and production environments.

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

ENV PORT=3001
ENV NODE_ENV=production

EXPOSE 3001

CMD ["node", "server.js"]

Webhook Support for External Integrations

The Forms API should provide webhook support to integrate with external systems:

Webhook Registration

Endpoint: POST /api/webhooks/register

Purpose: Register a webhook for form events

Request Headers:

Content-Type: application/json
Authorization: Bearer {admin_jwt_token}

Request Body:

{
"url": "https://external-system.example.com/webhook",
"events": ["form.submitted", "form.approved", "form.rejected"],
"secret": "your-webhook-secret-for-signature-verification",
"description": "Integration with HR system"
}

Success Response:

{
"success": true,
"message": "Webhook registered successfully",
"webhookId": "whk_1234567890"
}

Webhook Event Structure

When a form event occurs, the API will send a POST request to the registered webhook URL:

{
"event": "form.approved",
"timestamp": "2025-07-26T16:30:00Z",
"webhookId": "whk_1234567890",
"data": {
"submissionId": "1234567890",
"formId": "employee-onboarding",
"status": "approved",
"submittedBy": "John Doe",
"approvedBy": "HR Manager",
"approvedDate": "2025-07-26T16:30:00Z"
},
"signature": "sha256=..."
}

Webhook Security

To ensure webhook security:

  1. HTTPS: All webhook URLs must use HTTPS
  2. Signature Verification: Each webhook request includes a signature header
  3. Retry Logic: Failed webhook deliveries are retried with exponential backoff
  4. Monitoring: All webhook deliveries are logged for auditing purposes

Example signature verification code:

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');

return crypto.timingSafeEqual(
Buffer.from(`sha256=${expectedSignature}`),
Buffer.from(signature)
);
}

Monitoring and Maintenance

Implement monitoring using tools like:

  1. Prometheus: For metrics collection
  2. Grafana: For visualization and dashboards
  3. ELK Stack: For log aggregation and analysis

Regular maintenance should include:

  1. Security Updates: Keep all dependencies up to date
  2. Performance Optimization: Monitor and optimize API performance
  3. Feature Enhancements: Add new features based on user feedback

SLA and Support

The Forms API should operate with the following Service Level Agreements:

  1. Availability: 99.9% uptime (less than 9 hours of downtime per year)
  2. Response Time: 95% of requests completed within 500ms
  3. Support Response Times:
    • Critical issues: Response within 1 hour, resolution within 4 hours
    • High priority: Response within 4 hours, resolution within 24 hours
    • Medium priority: Response within 24 hours, resolution within 3 business days
    • Low priority: Response within 2 business days, resolution within 7 business days