PurrOSDocs

Attachments

Upload files that go with records (proof, photos, receipts, signed forms, payslips) and control who can see them.

Attachments hold files that go with records: proof for a time-off request or punch correction, photos of waste or a broken machine, delivery receipts, signed forms, payslip PDFs. Each file is stored in file storage (local disk or an S3 bucket), and PurrOS keeps its details: name, type, size, SHA-256 checksum, who uploaded it, and who it concerns.

An upload returns a url. Put that URL in any field that takes a link, for example:

  • a corrective action's evidenceUrl
  • a work order's photos
  • a waste record's photoUrl
  • an employee document's url
  • a payslip's url

Opening the URL goes through the same access check as the API.

Uploading

Send multipart/form-data with the file in a field named file:

curl -X POST https://erp.example.com/api/v1/attachments \
  -H "Authorization: Bearer $PURROS_KEY" \
  -F [email protected] -F purpose=receipt -F locationId=loc_01H…

Or send the file itself as the body, with the details as query parameters:

curl -X POST "https://erp.example.com/api/v1/attachments?name=payslip-2026-09.pdf&purpose=payslip&employeeId=emp_01H…" \
  -H "Authorization: Bearer $PURROS_KEY" -H "Content-Type: application/pdf" --data-binary @payslip.pdf
FieldDescription
fileThe file (multipart only)
nameFile name (defaults to the uploaded file's name)
purposeproof, photo, receipt, invoice, document, payslip, signature or other (default)
employeeIdThe employee it concerns. They can always see it.
locationIdThe location it belongs to, for managers' reach
noteUp to 1,000 characters

The response (201) contains:

  • id and url
  • contentType, sizeBytes and sha256
  • purpose, employeeId, locationId and uploadedBy

Accepted files

Accepted file types are:

  • images: JPEG, PNG, GIF, WebP, HEIC
  • PDF
  • plain text and CSV
  • Word, Excel, PowerPoint and OpenDocument files
  • MP4/MOV video, and audio

The type is detected from the file's content, not its name. HTML, SVG, scripts and programs are refused (422). Files larger than STORAGE_MAX_UPLOAD_MB (25 MB by default) are refused with 413 payload_too_large.

Downloading

GET /api/v1/attachments/{id}/content checks access, then:

  • with S3 storage, redirects (302) to a signed link that expires after STORAGE_SIGNED_URL_TTL seconds
  • with local storage, streams the file

Images, PDFs, video, audio and plain text open in the browser; other files download.

Who can see a file

CallerCan see
Integration keyEvery attachment, with attachments:read
The uploaderTheir own uploads
The employee it concerns (employeeId)Files about them, e.g. payslips or documents HR uploaded
Anyone elseNeeds attachments.read, with a reach covering the file's employee or location. A file with neither needs reach Everyone.

People can only tag files with their own employee record and home location, or with employees and locations inside their attachments.read reach. This stops a file being pushed into someone else's view.

Files a person can't see answer 404, as if they didn't exist.

Deleting

DELETE /api/v1/attachments/{id} removes the file from storage. Its record is kept for the audit log.

  • Uploaders can delete their own files.
  • Anyone else needs attachments.manage within reach, or an integration key with attachments:write.

Endpoints

EndpointIntegration scopePeople
POST /attachmentsattachments:writeanyone signed in
GET /attachments?employeeId=&locationId=&purpose=attachments:readattachments.read
GET /attachments/{id}attachments:readsee the access rules above
GET /attachments/{id}/contentattachments:readsee the access rules above
DELETE /attachments/{id}attachments:writeuploader, or attachments.manage
GET /me/attachments—files you uploaded or that concern you

Webhook events: attachment.uploaded, attachment.deleted.

Backups

With local storage, backups include every attachment's file by default (PURROS_BACKUP_FILES=auto), and backup restore puts them back. With S3 storage, rely on bucket versioning and replication, or set PURROS_BACKUP_FILES=true to include the files in backups too. See Backups & upgrades.

On this page