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 addressuserAgent(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 somethingneedsTermsAcceptance: true→ New terms version publisheduserTermsVersion !== currentTermsVersion→ Version mismatchrequiresImmediate: true→ Blocking modalrequiresImmediate: 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 titlecontent(required): Full document contenteffectiveDate(required): When document becomes effectiverequiresImmediate(optional): DefaulttruegracePeriodDays(optional): Default0
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, default100, max500offset(optional): Pagination offset, default0
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 versiontotalUsers: Total users in systemacceptanceRate: Percentage of users who acceptedisActive: Whether this is the current active version
Error Handling
Common HTTP Status Codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request (validation error) |
| 401 | Unauthorized (missing auth) |
| 403 | Forbidden (not admin) |
| 404 | Not Found |
| 500 | Server 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.