Documents API¶
Upload, download, and manage encrypted documents with classification levels.
Overview¶
The Documents API provides secure file storage with automatic encryption for sensitive documents. All documents are classified by sensitivity level, which determines encryption and retention policies.
Base URL: https://api.meister-bill.com/v1/documents
Authentication¶
All document endpoints require authentication via Bearer token:
Authorization: Bearer {jwt_token}
Document Classification¶
| Level | Encryption | Retention | Access |
|---|---|---|---|
public |
None | Unlimited | Anyone |
internal |
AES-256-GCM | 365 days | Organization members |
confidential |
AES-256-GCM | 90 days | Organization members |
restricted |
AES-256-GCM | 30 days | Owner only |
Endpoints¶
Upload Document¶
Upload a file with classification metadata.
POST /v1/documents/upload
Content-Type: multipart/form-data
Authorization: Bearer {token}
Classification-Level: confidential
Entity-Type: invoice
Entity-Id: 550e8400-e29b-41d4-a716-446655440000
Expires-At: 2025-05-01T00:00:00Z
Headers:
| Header | Required | Description |
|---|---|---|
Classification-Level |
Yes | Document classification level |
Entity-Type |
No | Associated entity type (invoice, quote, etc.) |
Entity-Id |
No | Associated entity UUID |
Expires-At |
No | Expiration date (ISO 8601) |
Body:
- file: Binary file data (multipart/form-data)
Response:
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440001",
"ownerId": "550e8400-e29b-41d4-a716-446655440002",
"filename": "invoice_001.pdf",
"originalName": "Invoice 001.pdf",
"documentType": "invoice",
"classification": "confidential",
"mimeType": "application/pdf",
"sizeBytes": 1048576,
"isEncrypted": true,
"entityType": "invoice",
"entityId": "550e8400-e29b-41d4-a716-446655440000",
"expiresAt": "2025-05-01T00:00:00Z",
"createdAt": "2025-02-26T12:00:00Z"
}
}
Errors:
| Status | Code | Description |
|---|---|---|
| 400 | invalid_classification |
Invalid classification level |
| 400 | file_too_large |
File exceeds size limit (50MB) |
| 401 | unauthorized |
Invalid or missing token |
| 413 | payload_too_large |
Request body too large |
Download Document¶
Download a document (automatically decrypted if encrypted).
GET /v1/documents/download/{fileId}
Authorization: Bearer {token}
Parameters:
| Name | Type | Description |
|---|---|---|
fileId |
UUID | Document ID from upload response |
Response: File stream with appropriate content type
Headers:
| Header | Value |
|---|---|
Content-Type |
Original MIME type |
Content-Disposition |
attachment; filename="{originalName}" |
Content-Length |
File size in bytes |
Cache-Control |
private, no-store |
X-Document-Classification |
Classification level |
Errors:
| Status | Code | Description |
|---|---|---|
| 403 | access_denied |
Insufficient permissions |
| 404 | not_found |
Document not found |
| 410 | expired |
Document has expired |
Get Document Metadata¶
Retrieve document metadata without downloading.
GET /v1/documents/{fileId}/metadata
Authorization: Bearer {token}
Response:
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440001",
"filename": "invoice_001.pdf",
"originalName": "Invoice 001.pdf",
"documentType": "invoice",
"classification": "confidential",
"mimeType": "application/pdf",
"sizeBytes": 1048576,
"isEncrypted": true,
"entityType": "invoice",
"entityId": "550e8400-e29b-41d4-a716-446655440000",
"expiresAt": "2025-05-01T00:00:00Z",
"createdAt": "2025-02-26T12:00:00Z"
}
}
Get Document Audit Log¶
Retrieve access history for a document.
GET /v1/documents/{fileId}/audit-log
Authorization: Bearer {token}
Response:
{
"success": true,
"data": {
"fileId": "550e8400-e29b-41d4-a716-446655440001",
"fileName": "invoice_001.pdf",
"entries": [
{
"id": "550e8400-e29b-41d4-a716-446655440003",
"action": "upload",
"actorId": "550e8400-e29b-41d4-a716-446655440002",
"actorEmail": "user@example.com",
"ipAddress": "192.168.1.1",
"userAgent": "Mozilla/5.0...",
"createdAt": "2025-02-26T12:00:00Z"
},
{
"id": "550e8400-e29b-41d4-a716-446655440004",
"action": "download",
"actorId": "550e8400-e29b-41d4-a716-446655440002",
"actorEmail": "user@example.com",
"ipAddress": "192.168.1.1",
"userAgent": "Mozilla/5.0...",
"createdAt": "2025-02-26T12:30:00Z"
}
]
}
}
Actions:
| Action | Description |
|---|---|
upload |
Document uploaded |
download |
Document downloaded |
delete |
Document deleted |
view |
Metadata viewed |
share |
Share link created |
expire |
Document expired |
Delete Document¶
Permanently delete a document.
DELETE /v1/documents/{fileId}
Authorization: Bearer {token}
Response:
{
"success": true,
"message": "Document deleted successfully"
}
Note: Only the document owner can delete. This action is logged and irreversible.
List Documents¶
List documents with optional filtering.
GET /v1/documents?entityType=invoice&entityId={id}&page=1&limit=20
Authorization: Bearer {token}
Query Parameters:
| Name | Type | Description |
|---|---|---|
entityType |
string | Filter by entity type |
entityId |
UUID | Filter by entity ID |
classification |
string | Filter by classification |
page |
number | Page number (default: 1) |
limit |
number | Items per page (default: 20, max: 100) |
Response:
{
"success": true,
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"filename": "invoice_001.pdf",
"originalName": "Invoice 001.pdf",
"classification": "confidential",
"mimeType": "application/pdf",
"sizeBytes": 1048576,
"isEncrypted": true,
"createdAt": "2025-02-26T12:00:00Z"
}
],
"meta": {
"page": 1,
"limit": 20,
"total": 150,
"pages": 8
}
}
Code Examples¶
Upload with JavaScript¶
async function uploadDocument(file, classification, entityType, entityId) {
const formData = new FormData()
formData.append('file', file)
const response = await fetch('https://api.meister-bill.com/v1/documents/upload', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Classification-Level': classification,
'Entity-Type': entityType,
'Entity-Id': entityId,
},
body: formData,
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.message)
}
return await response.json()
}
// Usage
const result = await uploadDocument(
fileInput.files[0],
'confidential',
'invoice',
'550e8400-e29b-41d4-a716-446655440000'
)
console.log('Uploaded:', result.data.id)
Download with Progress¶
async function downloadDocument(fileId, filename) {
const response = await fetch(`https://api.meister-bill.com/v1/documents/download/${fileId}`, {
headers: { 'Authorization': `Bearer ${token}` },
})
if (!response.ok) {
throw new Error(`Download failed: ${response.status}`)
}
const contentLength = +response.headers.get('Content-Length')
const reader = response.body.getReader()
const chunks = []
let received = 0
while (true) {
const { done, value } = await reader.read()
if (done) break
chunks.push(value)
received += value.length
const progress = (received / contentLength * 100).toFixed(1)
console.log(`Download progress: ${progress}%`)
}
// Combine chunks and create download
const blob = new Blob(chunks)
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename
a.click()
URL.revokeObjectURL(url)
}
List with Pagination¶
async function listDocuments(entityType, entityId, page = 1) {
const params = new URLSearchParams({
entityType,
entityId,
page: page.toString(),
limit: '20',
})
const response = await fetch(`https://api.meister-bill.com/v1/documents?${params}`, {
headers: { 'Authorization': `Bearer ${token}` },
})
return await response.json()
}
Limits¶
| Limit | Value |
|---|---|
| Max file size | 50 MB |
| Max request size | 55 MB |
| Max upload per hour | 100 files |
| Max downloads per hour | 1000 files |
Security Notes¶
-
Encryption Keys: Documents are encrypted with user-specific keys derived from a master key. The master key is stored only in Cloudflare Worker secrets.
-
Access Control: Access is strictly enforced based on document classification and ownership.
-
Audit Trail: Every access (upload, download, view, delete) is logged with IP address and timestamp.
-
Expiration: Documents automatically expire based on classification level. Expired documents are permanently deleted.
-
Download Security: Downloads use
Cache-Control: private, no-storeto prevent caching.
Error Codes¶
| Code | HTTP | Description |
|---|---|---|
invalid_classification |
400 | Invalid classification level |
file_too_large |
400 | File exceeds 50MB limit |
missing_file |
400 | No file in request |
unauthorized |
401 | Invalid or missing token |
access_denied |
403 | Insufficient permissions |
not_found |
404 | Document not found |
expired |
410 | Document has expired |
payload_too_large |
413 | Request body too large |
rate_limit_exceeded |
429 | Too many requests |
encryption_error |
500 | Encryption/decryption failed |
storage_error |
500 | R2 storage error |
*Last updated: 2026-02-26