File Attachments
Upload, manage, and attach files to records using the Coherence API
The Coherence API provides a complete file management system for uploading, storing, and attaching files to your records. Files are stored securely and accessed via time-limited signed URLs.
Overview
File attachments in Coherence follow a two-step process:
- Upload the file - Get a presigned upload URL and upload your file directly to storage
- Attach to record - Reference the file ID when creating or updating records
This approach enables efficient direct-to-storage uploads without proxying file data through the API.
Supported File Types
| Category | MIME Types | Max Size |
|---|---|---|
| Images | image/jpeg, image/png, image/gif, image/webp, image/svg+xml | 50 MB |
| Videos | video/mp4, video/quicktime, video/webm, video/x-msvideo | 50 MB |
| Documents | application/pdf | 50 MB |
Upload a File
Step 1: Request Upload URL
Request a presigned URL for uploading your file.
curl -X POST "https://api.getcoherence.io/v1/files/upload-url" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fileName": "document.pdf",
"mimeType": "application/pdf",
"fileSize": 1024000
}'Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
fileName | string | Yes | Original file name |
mimeType | string | Yes | MIME type of the file |
fileSize | number | Yes | File size in bytes (max 50 MB) |
Response:
{
"mediaId": "file_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"uploadUrl": "https://storage.example.com/upload?signature=...",
"expiresAt": "2024-01-15T11:30:00Z",
"key": "media-library/acc_123/file_a1b2c3d4.pdf"
}Step 2: Upload to Presigned URL
Upload your file directly to the presigned URL using a PUT request:
curl -X PUT "https://storage.example.com/upload?signature=..." \
-H "Content-Type: application/pdf" \
-H "Content-Length: 1024000" \
--data-binary @document.pdfThe presigned URL is valid for a limited time (typically 15 minutes). Upload your file before the URL expires.
Step 3: Confirm Upload
After uploading, confirm the upload to finalize the file record:
curl -X POST "https://api.getcoherence.io/v1/files/file_a1b2c3d4-e5f6-7890-abcd-ef1234567890/confirm" \
-H "Authorization: Bearer YOUR_API_KEY"Response:
{
"mediaId": "file_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"fileName": "document.pdf",
"mimeType": "application/pdf",
"fileSize": 1024000,
"width": null,
"height": null,
"source": "upload",
"createdAt": "2024-01-15T10:30:00Z"
}Get File Metadata
Retrieve metadata for a specific file:
curl -X GET "https://api.getcoherence.io/v1/files/file_a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer YOUR_API_KEY"Response:
{
"mediaId": "file_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"fileName": "document.pdf",
"mimeType": "application/pdf",
"fileSize": 1024000,
"width": null,
"height": null,
"source": "upload",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}For images, the response includes dimensions:
{
"mediaId": "file_b2c3d4e5-f6a7-8901-bcde-f23456789012",
"fileName": "photo.jpg",
"mimeType": "image/jpeg",
"fileSize": 524288,
"width": 1920,
"height": 1080,
"source": "upload",
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}Download a File
Get a signed download URL for a file:
curl -X GET "https://api.getcoherence.io/v1/files/file_a1b2c3d4-e5f6-7890-abcd-ef1234567890/download" \
-H "Authorization: Bearer YOUR_API_KEY"Response:
{
"url": "https://storage.example.com/download?signature=...",
"expiresAt": "2024-01-15T11:30:00Z"
}Download URLs expire after 1 hour. Request a new URL if the previous one has expired.
Delete a File
Permanently delete a file:
curl -X DELETE "https://api.getcoherence.io/v1/files/file_a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer YOUR_API_KEY"Returns 204 No Content on success.
Deleting a file removes it from storage permanently. This action cannot be undone.
Attach Files to Records
Once a file is uploaded, attach it to a record by setting the attachment field value:
curl -X POST "https://api.getcoherence.io/v1/modules/contacts/records" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "John Smith",
"email": "[email protected]",
"profile_photo": "file_b2c3d4e5-f6a7-8901-bcde-f23456789012",
"documents": [
"file_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"file_c3d4e5f6-a7b8-9012-cdef-345678901234"
]
}'Attachment fields can store:
- Single file: Use a string file ID
- Multiple files: Use an array of file IDs
Image Features
Thumbnails
For PDF documents, thumbnails are automatically generated. Request a thumbnail URL:
curl -X GET "https://api.getcoherence.io/v1/files/file_a1b2c3d4-e5f6-7890-abcd-ef1234567890/thumbnail" \
-H "Authorization: Bearer YOUR_API_KEY"Response:
{
"url": "https://storage.example.com/thumbnail?signature=...",
"expiresAt": "2024-01-15T11:30:00Z"
}Returns null if no thumbnail is available.
Image Dimensions
Image metadata automatically includes dimensions:
| Field | Type | Description |
|---|---|---|
width | number | Image width in pixels |
height | number | Image height in pixels |
API Reference
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /files/upload-url | Get presigned upload URL |
| POST | /files/{id}/confirm | Confirm file upload |
| GET | /files/{id} | Get file metadata |
| GET | /files/{id}/download | Get signed download URL |
| GET | /files/{id}/thumbnail | Get thumbnail URL (if available) |
| DELETE | /files/{id} | Delete a file |
File Object
| Field | Type | Description |
|---|---|---|
mediaId | string | Unique file identifier |
fileName | string | Original file name |
mimeType | string | MIME type |
fileSize | number | Size in bytes |
width | number | Image width (images only) |
height | number | Image height (images only) |
source | string | Source: upload, generated, or edited |
createdAt | string | ISO 8601 timestamp |
updatedAt | string | ISO 8601 timestamp |
Security
Signed URLs
All file access uses time-limited signed URLs:
- Upload URLs: Valid for 15 minutes
- Download URLs: Valid for 1 hour
Signed URLs contain cryptographic signatures that prevent tampering. They expire automatically and cannot be reused after expiration.
Access Control
Files are scoped to your workspace. API keys can only access files within their authorized workspace.
Best Practices
Upload Before Creating Records
Always upload files before creating or updating records that reference them:
// 1. Upload the file first
const uploadResponse = await fetch('/files/upload-url', {
method: 'POST',
body: JSON.stringify({
fileName: 'photo.jpg',
mimeType: 'image/jpeg',
fileSize: file.size
})
});
const { mediaId, uploadUrl } = await uploadResponse.json();
// 2. Upload to presigned URL
await fetch(uploadUrl, {
method: 'PUT',
body: file,
headers: { 'Content-Type': 'image/jpeg' }
});
// 3. Confirm the upload
await fetch(`/files/${mediaId}/confirm`, { method: 'POST' });
// 4. Create record with file reference
await fetch('/modules/contacts/records', {
method: 'POST',
body: JSON.stringify({
name: 'John Smith',
profile_photo: mediaId
})
});Handle Upload Progress
For large files, track upload progress using the XMLHttpRequest API or fetch with streams:
function uploadWithProgress(url: string, file: File, onProgress: (percent: number) => void) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
onProgress(Math.round((e.loaded / e.total) * 100));
}
});
xhr.addEventListener('load', () => resolve(xhr.response));
xhr.addEventListener('error', reject);
xhr.open('PUT', url);
xhr.setRequestHeader('Content-Type', file.type);
xhr.send(file);
});
}Validate File Types Client-Side
Validate file types before uploading to provide immediate feedback:
const ALLOWED_TYPES = [
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
'application/pdf',
'video/mp4'
];
const MAX_SIZE = 50 * 1024 * 1024; // 50 MB
function validateFile(file: File): { valid: boolean; error?: string } {
if (!ALLOWED_TYPES.includes(file.type)) {
return { valid: false, error: 'File type not supported' };
}
if (file.size > MAX_SIZE) {
return { valid: false, error: 'File exceeds 50 MB limit' };
}
return { valid: true };
}Cache Download URLs Appropriately
Download URLs expire after 1 hour. Cache them with appropriate TTLs:
const urlCache = new Map<string, { url: string; expiresAt: Date }>();
async function getDownloadUrl(fileId: string): Promise<string> {
const cached = urlCache.get(fileId);
// Use cached URL if still valid (with 5 min buffer)
if (cached && cached.expiresAt > new Date(Date.now() + 5 * 60 * 1000)) {
return cached.url;
}
// Fetch new URL
const response = await fetch(`/files/${fileId}/download`);
const { url, expiresAt } = await response.json();
urlCache.set(fileId, { url, expiresAt: new Date(expiresAt) });
return url;
}Error Handling
Common Errors
| Code | Message | Solution |
|---|---|---|
| 400 | Invalid file type | Use a supported MIME type |
| 400 | File too large | Reduce file size to under 50 MB |
| 404 | File not found | Verify the file ID is correct |
| 410 | Upload URL expired | Request a new upload URL |
Example Error Response
{
"error": {
"code": "file_too_large",
"message": "File size exceeds the maximum allowed size of 50 MB",
"details": {
"maxSize": 52428800,
"actualSize": 75000000
}
}
}Related: API Overview | Authentication