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:

  1. Upload the file - Get a presigned upload URL and upload your file directly to storage
  2. 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

CategoryMIME TypesMax Size
Imagesimage/jpeg, image/png, image/gif, image/webp, image/svg+xml50 MB
Videosvideo/mp4, video/quicktime, video/webm, video/x-msvideo50 MB
Documentsapplication/pdf50 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:

FieldTypeRequiredDescription
fileNamestringYesOriginal file name
mimeTypestringYesMIME type of the file
fileSizenumberYesFile 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.pdf

The 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:

FieldTypeDescription
widthnumberImage width in pixels
heightnumberImage height in pixels

API Reference

Endpoints

MethodEndpointDescription
POST/files/upload-urlGet presigned upload URL
POST/files/{id}/confirmConfirm file upload
GET/files/{id}Get file metadata
GET/files/{id}/downloadGet signed download URL
GET/files/{id}/thumbnailGet thumbnail URL (if available)
DELETE/files/{id}Delete a file

File Object

FieldTypeDescription
mediaIdstringUnique file identifier
fileNamestringOriginal file name
mimeTypestringMIME type
fileSizenumberSize in bytes
widthnumberImage width (images only)
heightnumberImage height (images only)
sourcestringSource: upload, generated, or edited
createdAtstringISO 8601 timestamp
updatedAtstringISO 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

CodeMessageSolution
400Invalid file typeUse a supported MIME type
400File too largeReduce file size to under 50 MB
404File not foundVerify the file ID is correct
410Upload URL expiredRequest 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