Skip to main content

Generate and upload a monthly compliance report

This example demonstrates how to generate a monthly compliance report in ONLYOFFICE DocSpace using the API. It retrieves audit events for a selected period, optionally filters events to focus on important documents, builds a human-readable TXT report, and uploads the report to a dedicated folder (for example, a “Compliance” room folder).

Before you start

  1. Replace https://yourportal.onlyoffice.com and YOUR_API_KEY with your actual DocSpace portal URL and API key. Ensure you have the necessary data and permissions to perform these operations.
  2. Before you can make requests to the API, you need to authenticate. Check out the Personal access tokens page to learn how to obtain and use access tokens.
Full example
// Set API base URL
const API_HOST = process.env.DOCSPACE_API_HOST; // Set DOCSPACE_API_HOST in env (recommended). For quick tests you can temporarily paste your portal URL here.
const API_KEY = process.env.DOCSPACE_API_KEY; // Set DOCSPACE_API_KEY in env (recommended). For quick tests you can temporarily paste token here.

// Folder where compliance reports will be stored
const COMPLIANCE_ROOM_FOLDER_ID = 'compliance_room_folder_id_here';

// Headers with API key for authentication (JSON requests)
const HEADERS = {
Accept: 'application/json',
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
};

// Step 1: Retrieve audit events for a given period
async function getAuditEventsByFilter(startDate?: string, endDate?: string) {
const url = new URL(`${API_HOST}/api/2.0/security/audit/events/filter`);
if (startDate) url.searchParams.set('from', startDate);
if (endDate) url.searchParams.set('to', endDate);

const res = await fetch(url.toString(), { method: 'GET', headers: HEADERS });
if (!res.ok) {
const t = await res.text();
console.log(`Audit events retrieval failed. Status code: ${res.status}, Message: ${t}`);
return null;
}

const json = await res.json();
console.log('Audit events retrieved successfully.');
return json;
}

// Step 2: Filter events for important documents (simple example)
function filterImportantEvents(auditEvents: any[]) {
// In a real-world scenario, you might filter by:
// - roomId / folderId
// - fileId list
// - event type (create, update, delete, share)
// This example simply returns all events as "important".
return auditEvents;
}

// Step 3: Build a human-readable text report
function buildComplianceReportText(fromDate: string, toDate: string, events: any[]) {
const lines: string[] = [];
lines.push('Compliance report');
lines.push(`Period: ${fromDate} - ${toDate}`);
lines.push(`Total events: ${events.length}`);
lines.push('');
lines.push('Events:');
lines.push('-'.repeat(80));

for (const e of events) {
const dateStr = e.date || e.time || '';
const user = e.user || e.userId || e.createdBy || e.createBy || '';
const action = e.action || e.eventType || '';
const target = e.target || e.targetId || e.fileId || e.folderId || '';
const ip = e.ip || '';
const country = e.country || '';
const city = e.city || '';
const browser = e.browser || '';
const platform = e.platform || '';
const page = e.page || '';
const context = e.context || '';

lines.push(`ID: ${e.id}`);
lines.push(`Date: ${dateStr}`);
lines.push(`User: ${user}`);
lines.push(`Action: ${action}`);
lines.push(`Target: ${target}`);
if (context) lines.push(`Context: ${context}`);
if (page) lines.push(`Page: ${page}`);
if (ip || country || city) lines.push(`Location/IP: ${ip} | ${country}, ${city}`);
if (browser || platform) lines.push(`Client: ${browser} on ${platform}`);
lines.push('-'.repeat(80));
}

return lines.join('\n');
}

// Step 4: Upload the report as a TXT file into the Compliance room
async function uploadReportToComplianceFolder(folderId: string, fileName: string, content: string) {
// Correct endpoint: /api/2.0/files/{folderId}/upload
const url = `${API_HOST}/api/2.0/files/${folderId}/upload`;

const form = new FormData();
form.append('file', new Blob([content], { type: 'text/plain' }), fileName);

// For file upload, set Authorization only; the browser/runtime will set multipart headers
const headers = {
Authorization: `Bearer ${API_KEY}`,
};

const res = await fetch(url, { method: 'POST', headers, body: form });
if (!res.ok) {
const t = await res.text();
console.log(`Report upload failed. Status code: ${res.status}, Message: ${t}`);
return null;
}

const json = await res.json();
console.log('Compliance report uploaded successfully.');
return json;
}

// Main workflow: generate a monthly compliance report
(async () => {
try {
// Example: January 2025
const year = 2025;
const month = 1;

// Simple date range: start = first day, end = day 31 (demo)
const fromDate = `${year}-${String(month).padStart(2, '0')}-01`;
const toDate = `${year}-${String(month).padStart(2, '0')}-31`;

console.log('\nStep 1: Retrieving audit events for the month...');
const auditData = await getAuditEventsByFilter(fromDate, toDate);
if (!auditData) return;

// In DocSpace audit API the events are usually under "response"
let events = (auditData.response ?? auditData) as any[];
if (!Array.isArray(events)) events = [];

console.log(`Total audit events retrieved: ${events.length}`);

console.log('\nStep 2: Filtering important events...');
const importantEvents = filterImportantEvents(events);
console.log(`Important events count: ${importantEvents.length}`);

console.log('\nStep 3: Building compliance report content...');
const reportContent = buildComplianceReportText(fromDate, toDate, importantEvents);

const reportFileName = `compliance-report-${year}-${String(month).padStart(2, '0')}.txt`;

console.log('\nStep 4: Uploading the report to the Compliance room...');
await uploadReportToComplianceFolder(COMPLIANCE_ROOM_FOLDER_ID, reportFileName, reportContent);

console.log('\nMonthly compliance report generation completed.');
} catch (err: any) {
console.error(err?.message || err);
}
})();

Step 1: Retrieve audit events for the month

A GET request is sent to /api/2.0/security/audit/events/filter to retrieve audit events for a selected period.

The request uses query parameters:

  • from: Period start date (YYYY-MM-DD)
  • to: Period end date (YYYY-MM-DD)

The API returns a list of audit events (typically under response).

async function getAuditEventsByFilter(startDate?: string, endDate?: string) {
const url = new URL(`${API_HOST}/api/2.0/security/audit/events/filter`);
if (startDate) url.searchParams.set('from', startDate);
if (endDate) url.searchParams.set('to', endDate);

const res = await fetch(url.toString(), { method: 'GET', headers: HEADERS });
if (!res.ok) {
const t = await res.text();
console.log(`Audit events retrieval failed. Status code: ${res.status}, Message: ${t}`);
return null;
}

const json = await res.json();
console.log('Audit events retrieved successfully.');
return json;
}

Step 2: Filter important events

This step filters audit events to keep only events relevant for compliance reporting.

In a real scenario, you might filter by:

  • room or folder ID
  • Specific file IDs
  • Event type (create, update, delete, share)

This example keeps all events for simplicity.

function filterImportantEvents(auditEvents: any[]) {
// In a real-world scenario, you might filter by:
// - roomId / folderId
// - fileId list
// - event type (create, update, delete, share)
// This example simply returns all events as "important".
return auditEvents;
}

Step 3: Build a TXT compliance report

This step converts raw audit events into a human-readable text report:

  • Report header with period and event count
  • List of events with key fields (date, user, action, target, IP/location, client)

This format is easy to archive and review.

function buildComplianceReportText(fromDate: string, toDate: string, events: any[]) {
const lines: string[] = [];
lines.push('Compliance report');
lines.push(`Period: ${fromDate} - ${toDate}`);
lines.push(`Total events: ${events.length}`);
lines.push('');
lines.push('Events:');
lines.push('-'.repeat(80));

for (const e of events) {
const dateStr = e.date || e.time || '';
const user = e.user || e.userId || e.createdBy || e.createBy || '';
const action = e.action || e.eventType || '';
const target = e.target || e.targetId || e.fileId || e.folderId || '';
const ip = e.ip || '';
const country = e.country || '';
const city = e.city || '';
const browser = e.browser || '';
const platform = e.platform || '';
const page = e.page || '';
const context = e.context || '';

lines.push(`ID: ${e.id}`);
lines.push(`Date: ${dateStr}`);
lines.push(`User: ${user}`);
lines.push(`Action: ${action}`);
lines.push(`Target: ${target}`);
if (context) lines.push(`Context: ${context}`);
if (page) lines.push(`Page: ${page}`);
if (ip || country || city) lines.push(`Location/IP: ${ip} | ${country}, ${city}`);
if (browser || platform) lines.push(`Client: ${browser} on ${platform}`);
lines.push('-'.repeat(80));
}

return lines.join('\n');
}

Step 4: Upload the report to the Compliance folder

A POST request is sent to /api/2.0/files/:folderId/upload to upload the report as a TXT file into a selected folder. The request is sent as multipart/form-data with a single uploaded file. The response confirms the upload and returns file information.

async function uploadReportToComplianceFolder(folderId: string, fileName: string, content: string) {
// Correct endpoint: /api/2.0/files/{folderId}/upload
const url = `${API_HOST}/api/2.0/files/${folderId}/upload`;

const form = new FormData();
form.append('file', new Blob([content], { type: 'text/plain' }), fileName);

// For file upload, set Authorization only; the browser/runtime will set multipart headers
const headers = {
Authorization: `Bearer ${API_KEY}`,
};

const res = await fetch(url, { method: 'POST', headers, body: form });
if (!res.ok) {
const t = await res.text();
console.log(`Report upload failed. Status code: ${res.status}, Message: ${t}`);
return null;
}

const json = await res.json();
console.log('Compliance report uploaded successfully.');
return json;
}