Attachments
All examples assume the environment variables defined in REST API Overview:
export BASE_URL="http://localhost:3000/api"
export API_KEY="<paste your API key secret here>"
export ORG_ID="<organization uuid in scope>"
export TENANT_ID="<tenant uuid in scope>"
export RECORD_ID="<entity record id you want to attach files to>"
- Pass
X-Api-Key: $API_KEYon every request. API-key calls must always includeorganizationIdandtenantIdin payloads. - Required features:
attachments.viewfor read-only endpoints,attachments.managefor uploads, updates, transfers, partitions, and deletes. - Custom fields can be written with either
cf_<slug>keys or acustomFieldsobject. Multipart uploads accept a JSON string forcustomFields.
OCR / text extraction
Open Saasframe supports two text-extraction methods for attachments:
- LLM-based OCR (recommended) – uses OpenAI's GPT-4o vision capabilities for images and PDFs
- Pure-JS extraction – in-process fallback for plain text, PDFs, and DOCX files when LLM OCR is unavailable
LLM-based OCR (images and PDFs)
Environment Variables
Configure LLM-based OCR with the following variables in your .env:
# Required for LLM-based OCR
OPENAI_API_KEY=your_openai_api_key_here
# Optional: OCR model selection (default: gpt-4o)
# Supported models: gpt-4o, gpt-4o-mini
OCR_MODEL=gpt-4o
# Optional: Custom prompt for text extraction
# OCR_DEFAULT_PROMPT="Extract all text content from this image..."
# Optional attachment defaults and limits
# SF_DEFAULT_ATTACHMENT_OCR_ENABLED=true
# SF_ATTACHMENT_MAX_UPLOAD_MB=25
# SF_ATTACHMENT_TENANT_QUOTA_MB=512
Attachment env naming follows the standard SF_* prefix. Legacy SAASFRAME_* aliases are still read for backward compatibility, but new deployments should use the SF_* names.
How it works
- Images (PNG, JPG, GIF, BMP, WebP, TIFF): Sent directly to GPT-4o for text extraction
- PDFs: Parsed with
pdfjs-dist; text-layer content is used directly when available, and scanned/image-only pages are rendered through the Node canvas backend before OCR - Processing: Asynchronous – uploads return immediately, content is populated in the background
- Model selection: Per-partition model override available in partition settings UI
- Extracted text: Stored in
Attachment.contentfield and shown in the metadata dialog - Security: No
pdf2pic, GraphicsMagick, Ghostscript, or external binaries are used in any extraction path - Fallback: when LLM OCR is unavailable, plain text files are read directly, PDFs are parsed with
pdfjs-dist, and DOCX files are parsed withmammoth
Pure-JS extraction (plain text, PDF, DOCX)
No additional setup required — extraction runs in-process with no external binaries.
Supported formats and their extractors:
| Format | Extractor |
|---|---|
Plain text (text/*, .txt, .md, .csv, .log) | Direct UTF-8 read |
PDF (.pdf) | pdfjs-dist — text layer content |
Word document (.docx) | mammoth |
Formats not extracted (returned as no content): .doc (legacy binary Word), .xlsx, .xls, .pptx, .ppt, .msg, and other Office/Outlook formats. LLM-based OCR remains available for these files where the partition is configured with an OCR model.
Configuration
SF_DEFAULT_ATTACHMENT_OCR_ENABLED=true|falsecontrols whether new partitions opt into OCR by default.SF_ATTACHMENT_MAX_UPLOAD_MB=<number>sets the global upload cap per file before field-specific limits are applied. Default:25.SF_ATTACHMENT_TENANT_QUOTA_MB=<number>sets the tenant-wide stored attachment quota. Default:512.- Each partition has a
requiresOcrflag; disable it to skip extraction for that partition. - Partition-specific model selection is available in the partition settings UI for image/PDF OCR.
Attachments per record
List attachments — GET /attachments?entityId=<id>&recordId=<id>
Feature: attachments.view
Returns files assigned to a specific entity record, ordered newest first.
curl -X GET "$BASE_URL/attachments?entityId=catalog:catalog_product&recordId=$RECORD_ID" \
-H "X-Api-Key: $API_KEY" \
-H "Accept: application/json"
The payload includes id, fileName, fileSize, createdAt, partitionCode, url, thumbnailUrl, tags, and assignments.
Upload attachment — POST /attachments (multipart)
Feature: attachments.manage
Fields:
entityIdandrecordId— required.file— required binary part.- Optional:
fieldKey(validates max size/extension + partition overrides from custom-field definitions),partitionCodeoverride,tags(JSON string array),assignments(JSON string array of{ type, id, href?, label? }),customFieldsJSON map.
curl -X POST "$BASE_URL/attachments" \
-H "X-Api-Key: $API_KEY" \
-F "entityId=catalog:catalog_product" \
-F "recordId=$RECORD_ID" \
-F "file=@./hero.jpg" \
-F "tags=[\"hero\",\"web\"]"
Response contains the stored item plus a prebuilt thumbnailUrl (/api/attachments/image/{id}).
Delete attachment — DELETE /attachments?id=<uuid>
Feature: attachments.manage
curl -X DELETE "$BASE_URL/attachments?id=$ATTACHMENT_ID" \
-H "X-Api-Key: $API_KEY"
Deletes the record, purges cached thumbnails, and removes the stored asset.
Attachment library
Use the library endpoints to browse or edit attachments across entities.
Search library — GET /attachments/library
Feature: attachments.view
Query parameters: page (default 1), pageSize (max 100), search (file name substring), partition, tags (comma separated), sortField (fileName, fileSize, createdAt), sortDir.
curl -X GET "$BASE_URL/attachments/library?page=1&pageSize=20&tags=hero" \
-H "X-Api-Key: $API_KEY"
Response includes paginated items, available partitions, and discovered tag list.
Retrieve attachment — GET /attachments/library/{id}
Feature: attachments.view
Returns metadata for a single attachment: base fields, tags, assignments (enriched with labels/links when possible), partition label, and any custom field values.
Update metadata — PATCH /attachments/library/{id}
Feature: attachments.manage
Body accepts tags (array), assignments (array of { type, id, href?, label? }), plus optional custom-field keys (cf_<slug> or customFields). Returns the updated tags/assignments/custom fields.
Delete attachment — DELETE /attachments/library/{id}
Feature: attachments.manage
Removes the attachment and its stored file.
Transfer attachments between records
Endpoint: POST /attachments/transfer
Feature: attachments.manage
Move multiple attachments within the same entity to a new record id.
curl -X POST "$BASE_URL/attachments/transfer" \
-H "X-Api-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"entityId": "catalog:catalog_product",
"attachmentIds": ["'$ATTACHMENT_ID'"],
"fromRecordId": "old-record-id",
"toRecordId": "new-record-id"
}'
Assignments pointing at fromRecordId are rewritten to toRecordId.
Storage partitions
Partition endpoints let you route uploads to different buckets or visibility tiers.
- List partitions —
GET /attachments/partitions(attachments.manage)
Ensures default partitions exist and returnscode,title,description,isPublic,requiresOcr,envKey. - Create partition —
POST /attachments/partitions(attachments.manage)
Body:code,title, optionaldescription,isPublic,requiresOcr. Fails when the environment locks partition management (demo/onboarding). - Update partition —
PUT /attachments/partitions(attachments.manage)
Body:id,code(immutable),title, optionaldescription,isPublic,requiresOcr. - Delete partition —
DELETE /attachments/partitions?id=<uuid>(attachments.manage)
Default partitions cannot be removed; partitions in use return409.
File delivery and imaging
These routes stream files and thumbnails. Access depends on partition visibility:
-
Public partitions: no token required unless
requireAuthForPublicis enabled server-side. -
Private partitions: require auth; scope must match attachment
organizationId/tenantIdor be a superadmin. -
Download original file —
GET /attachments/file/{id}
Add?download=1to force a download response. Respects partition cache headers. -
Serve/rescale image —
GET /attachments/image/{id}/{slug?}?width=<n>&height=<n>&cropType=cover|contain
Returns a resized image (Sharp) and caches thumbnails per size. Omitwidth/heightto stream the original image type while keeping access controls in place.