Photos¶
Available to: Owner, Project Manager, Supervisor, Badge Worker (assigned jobs only)
Photos can be attached to bids and jobs to document site conditions, progress, materials, and any other visual record relevant to the project.
Where Photos Can Be Attached¶
Photos are supported on two entity types:
- Bids — to document pre-work site conditions, measurements, or supporting materials.
- Jobs — to document work in progress, completion state, or issues encountered.
Photos are not currently supported on invoices or updates directly. To attach a photo to an update, post it as a job or bid photo alongside the update.
Upload Constraints¶
| Constraint | Limit |
|---|---|
| Allowed file types | JPEG, PNG, WEBP, HEIC |
| Maximum file size | 15 MB per file |
| Maximum files per upload | 10 files per request |
Uploads exceeding 15 MB per file or 10 files per request will be rejected with a validation error. Split large batches into multiple uploads.
HEIC Auto-Conversion¶
HEIC is the default format for photos taken on modern iPhones. Hardhat Flow accepts HEIC files but converts them automatically:
- The original HEIC file is uploaded to DigitalOcean Spaces.
- A Celery background task picks up the conversion job.
- The file is converted to JPEG and stored, replacing the reference on the photo record.
- The original HEIC file is removed from storage after successful conversion.
HEIC conversion is asynchronous. The photo will show as processing briefly after upload. Refresh to see the converted JPEG.
Thumbnail Generation¶
For every uploaded photo (after any HEIC conversion), a thumbnail is generated asynchronously:
- Thumbnails are 400px wide, with height proportional to the original aspect ratio.
- Generated by a Celery worker task — not during the upload request.
- Stored in DigitalOcean Spaces alongside the full-resolution file.
- Used in list views and preview panels to reduce page load times.
Full-resolution files are always preserved. Thumbnails are display-only.
Who Can Upload¶
| Role | Can Upload to Bids | Can Upload to Jobs |
|---|---|---|
| Owner | Yes — any bid | Yes — any job |
| Project Manager | Yes — their assigned bids | Yes — their assigned jobs |
| Supervisor | No | Yes — their assigned jobs |
| Badge Worker | No | Yes — jobs they are assigned to |
| Accounting | No | No |
Who Can Delete¶
Photo deletion is controlled by authorship and role:
- Authors may delete their own photos at any time.
- Owners may delete any photo regardless of who uploaded it.
- No other role can delete photos.
All photo deletes in Hardhat Flow are soft deletes. The file is NOT removed from DigitalOcean Spaces — only the database record is flagged as deleted. Only Platform Admin can initiate soft-deletes.
Soft Delete Behavior¶
When a photo is soft-deleted:
- The photo record in the database is flagged with a
deleted_attimestamp. - The record no longer appears in API responses or the UI.
- The file itself remains in DigitalOcean Spaces and is not purged.
- The deletion is recorded in the ActivityLog.
This approach ensures that photos can be recovered if a delete was made in error, and that storage audits remain accurate.
Physical file removal from DigitalOcean Spaces requires Platform Admin action and is handled outside the normal application workflow.
API Reference¶
List Photos¶
GET /{tenant-slug}/api/v1/photos/{bids|jobs}/{entity-id}/
Returns a paginated list of non-deleted photos for the given entity.
Response envelope:
{
"count": 3,
"next": null,
"previous": null,
"results": [
{
"id": "uuid",
"entity_type": "job",
"entity_id": "uuid",
"uploaded_by_id": "uuid",
"uploaded_by_name": "Jane Doe",
"file_url": "https://...",
"thumbnail_url": "https://...",
"created_at": "2026-04-07T10:00:00Z"
}
]
}
thumbnail_url will be an empty string immediately after upload and populated once the Celery task completes.
Upload Photos¶
POST /{tenant-slug}/api/v1/photos/{bids|jobs}/{entity-id}/
Content-Type: multipart/form-data
Field: photos — one or more files (max 10 per request).
Validation errors (400):
| Condition | Error message |
|---|---|
| No files provided | At least one file is required. |
| More than 10 files | Maximum 10 files per upload request. |
| Unsupported file type | Unsupported file type '...'. Allowed: JPEG, PNG, WEBP, HEIC. |
| File exceeds 15 MB | 'filename' exceeds the 15 MB file size limit. |
Success (201): Array of created photo objects (same shape as list results).
Delete a Photo¶
DELETE /{tenant-slug}/api/v1/photos/{photo-id}/
Soft-deletes the photo. The database record is flagged as deleted; the file in DigitalOcean Spaces is preserved.
| Response | Condition |
|---|---|
204 No Content |
Success |
403 Forbidden |
Caller is not the author, Owner, or Platform Admin |
404 Not Found |
Photo does not exist or is already deleted |
Storage Layout¶
Files are stored in DigitalOcean Spaces under the following path structure:
/{tenant-slug}/{entity-type}/{entity-id}/{photo-uuid}.{ext}
Thumbnails are stored at:
/{tenant-slug}/{entity-type}/{entity-id}/thumbnails/{photo-uuid}.jpg