Skip to main content

Legal Documents API Reference

Overview

The Legal Documents API provides endpoints for managing Terms of Service and Privacy Policy documents, tracking user acceptances, and retrieving compliance records.

Base URL

/legal

Authentication

  • Public Endpoints: No authentication required
  • Admin Endpoints: Requires AdminClerkAuthGuard (JWT token + email whitelist)
  • User Endpoints: Requires ClerkAuthGuard (JWT token)

Data Models

LegalDocument

interface LegalDocument {
id: string;
type: "terms" | "privacy";
version: string;
title: string;
content: string;
effectiveDate: Date;
publishedAt?: Date;
publishedBy?: string;
isActive: boolean;
requiresImmediate: boolean;
gracePeriodDays: number;
createdAt: Date;
updatedAt: Date;
acceptances?: DocumentAcceptance[];
}

DocumentAcceptance

interface DocumentAcceptance {
id: string;
userId: string;
documentId: string;
version: string;
acceptedAt: Date;
ipAddress?: string;
userAgent?: string;
}

TermsStatus

interface TermsStatus {
needsAcceptance: boolean;
needsTermsAcceptance: boolean;
needsPrivacyAcceptance: boolean;
currentTermsVersion: string | null;
currentPrivacyVersion: string | null;
userTermsVersion: string | null;
userPrivacyVersion: string | null;
termsRequiresImmediate: boolean;
privacyRequiresImmediate: boolean;
termsGracePeriodDays: number;
privacyGracePeriodDays: number;
}

Endpoints

Public Endpoints

GET /legal/current/:type

Get the currently active legal document for a type.

Parameters:

  • type (path): "terms" or "privacy"

Response (200 OK):

{
"id": "cuid_123",
"type": "terms",
"version": "2.0.0",
"title": "Terms of Service",
"content": "Full legal text...",
"effectiveDate": "2024-12-01T00:00:00Z",
"publishedAt": "2024-12-01T10:00:00Z",
"publishedBy": "admin@civstart.com",
"isActive": true,
"requiresImmediate": true,
"gracePeriodDays": 0,
"createdAt": "2024-12-01T09:00:00Z",
"updatedAt": "2024-12-01T10:00:00Z"
}

Error (404 Not Found):

{
"statusCode": 404,
"message": "No active terms document found"
}

POST /legal/accept

Record that a user has accepted a legal document.

Authentication: Required (ClerkAuthGuard)

Request Body:

{
"type": "terms",
"ipAddress": "192.168.1.1",
"userAgent": "Mozilla/5.0..."
}

Parameters:

  • type (body): "terms" or "privacy"
  • ipAddress (body, optional): Client IP address
  • userAgent (body, optional): Client user agent string

Response (201 Created):

{
"id": "cuid_456",
"userId": "user_123",
"documentId": "cuid_123",
"version": "2.0.0",
"acceptedAt": "2024-12-12T15:30:00Z",
"ipAddress": "192.168.1.1",
"userAgent": "Mozilla/5.0..."
}

Error (400 Bad Request):

{
"statusCode": 400,
"message": "No active terms document found"
}

GET /legal/status

Get the current user's terms acceptance status.

Authentication: Required (ClerkAuthGuard)

Response (200 OK):

{
"needsAcceptance": false,
"needsTermsAcceptance": false,
"needsPrivacyAcceptance": false,
"currentTermsVersion": "2.0.0",
"currentPrivacyVersion": "1.5.0",
"userTermsVersion": "2.0.0",
"userPrivacyVersion": "1.5.0",
"termsRequiresImmediate": true,
"privacyRequiresImmediate": false,
"termsGracePeriodDays": 0,
"privacyGracePeriodDays": 7
}

Interpretation:

  • needsAcceptance: true → User must accept something
  • needsTermsAcceptance: true → New terms version published
  • userTermsVersion !== currentTermsVersion → Version mismatch
  • requiresImmediate: true → Blocking modal
  • requiresImmediate: false → Show grace period banner

Admin Endpoints

All admin endpoints require AdminClerkAuthGuard.

GET /legal/admin/documents

List all legal documents with optional filtering.

Query Parameters:

  • type (optional): Filter by "terms" or "privacy"

Response (200 OK):

[
{
"id": "cuid_123",
"type": "terms",
"version": "2.0.0",
"title": "Terms of Service v2",
"isActive": true,
"publishedAt": "2024-12-01T10:00:00Z",
"publishedBy": "admin@civstart.com",
"createdAt": "2024-12-01T09:00:00Z",
"updatedAt": "2024-12-01T10:00:00Z",
"acceptances": [
{
"userId": "user_1",
"acceptedAt": "2024-12-01T15:00:00Z"
}
],
"acceptanceCount": 245
}
]

GET /legal/admin/documents/:id

Get a specific document with full details.

Parameters:

  • id (path): Document ID

Response (200 OK):

{
"id": "cuid_123",
"type": "terms",
"version": "2.0.0",
"title": "Terms of Service v2",
"content": "Full legal text...",
"effectiveDate": "2024-12-01T00:00:00Z",
"publishedAt": "2024-12-01T10:00:00Z",
"publishedBy": "admin@civstart.com",
"isActive": true,
"requiresImmediate": true,
"gracePeriodDays": 0,
"createdAt": "2024-12-01T09:00:00Z",
"updatedAt": "2024-12-01T10:00:00Z",
"acceptances": [...]
}

Error (404 Not Found):

{
"statusCode": 404,
"message": "Document not found"
}

POST /legal/admin/documents

Create a new document version (as draft).

Request Body:

{
"type": "terms",
"version": "2.0.0",
"title": "Terms of Service v2",
"content": "Full legal text...",
"effectiveDate": "2024-12-01T00:00:00Z",
"requiresImmediate": true,
"gracePeriodDays": 0
}

Parameters:

  • type (required): "terms" or "privacy"
  • version (required): Semantic version (e.g., "2.0.0")
  • title (required): Display title
  • content (required): Full document content
  • effectiveDate (required): When document becomes effective
  • requiresImmediate (optional): Default true
  • gracePeriodDays (optional): Default 0

Response (201 Created):

{
"id": "cuid_new",
"type": "terms",
"version": "2.0.0",
"title": "Terms of Service v2",
"content": "Full legal text...",
"effectiveDate": "2024-12-01T00:00:00Z",
"publishedAt": null,
"isActive": false,
"createdAt": "2024-12-12T12:00:00Z",
"updatedAt": "2024-12-12T12:00:00Z"
}

Error (400 Bad Request):

{
"statusCode": 400,
"message": "Document with type 'terms' and version '2.0.0' already exists"
}

PUT /legal/admin/documents/:id

Update an unpublished document draft.

Parameters:

  • id (path): Document ID

Request Body:

{
"title": "Updated Title",
"content": "Updated content...",
"effectiveDate": "2024-12-01T00:00:00Z",
"requiresImmediate": false,
"gracePeriodDays": 7
}

Response (200 OK):

{
"id": "cuid_123",
"type": "terms",
"version": "2.0.0",
"title": "Updated Title",
"content": "Updated content...",
"effectiveDate": "2024-12-01T00:00:00Z",
"requiresImmediate": false,
"gracePeriodDays": 7,
"publishedAt": null,
"isActive": false,
"updatedAt": "2024-12-12T13:00:00Z"
}

Error (400 Bad Request):

{
"statusCode": 400,
"message": "Cannot update a published document. Create a new version instead."
}

POST /legal/admin/documents/:id/publish

Publish a document version (make it active).

Parameters:

  • id (path): Document ID

Request Body:

{
"requiresImmediate": true,
"gracePeriodDays": 0
}

Response (200 OK):

{
"id": "cuid_123",
"type": "terms",
"version": "2.0.0",
"title": "Terms of Service v2",
"content": "Full legal text...",
"effectiveDate": "2024-12-01T00:00:00Z",
"publishedAt": "2024-12-12T14:00:00Z",
"publishedBy": "admin@civstart.com",
"isActive": true,
"requiresImmediate": true,
"gracePeriodDays": 0,
"createdAt": "2024-12-01T09:00:00Z",
"updatedAt": "2024-12-12T14:00:00Z"
}

Side Effects:

  • Previous active version of same type → isActive: false (archived)
  • All users → Must accept new version
  • Email notifications → (optional future feature)

Error (400 Bad Request):

{
"statusCode": 400,
"message": "This document is already published"
}

DELETE /legal/admin/documents/:id

Delete an unpublished document draft.

Parameters:

  • id (path): Document ID

Response (200 OK):

{
"success": true
}

Error (400 Bad Request):

{
"statusCode": 400,
"message": "Cannot delete a published document"
}

GET /legal/admin/acceptances

List user acceptance records with filtering and pagination.

Query Parameters:

  • type (optional): Filter by "terms" or "privacy"
  • limit (optional): Records per page, default 100, max 500
  • offset (optional): Pagination offset, default 0

Response (200 OK):

{
"acceptances": [
{
"id": "cuid_1",
"user": {
"id": "user_123",
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe"
},
"document": {
"type": "terms",
"version": "2.0.0",
"title": "Terms of Service"
},
"acceptedAt": "2024-12-12T15:30:00Z",
"ipAddress": "192.168.1.100",
"userAgent": "Mozilla/5.0..."
}
],
"total": 245,
"limit": 100,
"offset": 0
}

CSV Export: Can be used to generate CSV with headers:

User Email,User Name,Document Type,Version,Accepted At,IP Address,User Agent
user@example.com,John Doe,terms,2.0.0,2024-12-12T15:30:00Z,192.168.1.100,Mozilla/5.0...

GET /legal/admin/documents/:id/analytics

Get acceptance analytics for a document.

Parameters:

  • id (path): Document ID

Response (200 OK):

{
"documentId": "cuid_123",
"type": "terms",
"version": "2.0.0",
"totalAcceptances": 245,
"totalUsers": 300,
"acceptanceRate": 81.67,
"isActive": true,
"publishedAt": "2024-12-01T10:00:00Z"
}

Metrics:

  • totalAcceptances: Number of users who accepted this version
  • totalUsers: Total users in system
  • acceptanceRate: Percentage of users who accepted
  • isActive: Whether this is the current active version

Error Handling

Common HTTP Status Codes

CodeMeaning
200Success
201Created
400Bad Request (validation error)
401Unauthorized (missing auth)
403Forbidden (not admin)
404Not Found
500Server Error

Error Response Format

{
"statusCode": 400,
"message": "Human-readable error message",
"error": "BadRequestException"
}

Usage Examples

Example 1: User Accepting Terms

# First, get current status
curl -X GET https://api.civstart.com/legal/status \
-H "Authorization: Bearer USER_TOKEN"

# Response shows needsAcceptance: true

# Then accept
curl -X POST https://api.civstart.com/legal/accept \
-H "Authorization: Bearer USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "terms",
"userAgent": "Mozilla/5.0...",
"ipAddress": "192.168.1.1"
}'

Example 2: Admin Publishing New Terms

# Create draft
curl -X POST https://api.civstart.com/legal/admin/documents \
-H "Authorization: Bearer ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "terms",
"version": "2.0.0",
"title": "Updated Terms",
"content": "New terms content...",
"effectiveDate": "2024-12-15T00:00:00Z",
"requiresImmediate": true,
"gracePeriodDays": 0
}'

# Publish
curl -X POST https://api.civstart.com/legal/admin/documents/{ID}/publish \
-H "Authorization: Bearer ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"requiresImmediate": true,
"gracePeriodDays": 0
}'

Example 3: Exporting Acceptance Records

# Get all acceptances
curl -X GET "https://api.civstart.com/legal/admin/acceptances?limit=10000" \
-H "Authorization: Bearer ADMIN_TOKEN"

# Parse response and convert to CSV

Rate Limiting

Currently no rate limiting is enforced. This may be added in future versions.


Webhooks

Webhook support for acceptance events is planned for a future release.