API reference
Library assets
Tenant-scoped image library. Upload an image once, then reference it by file_id across multiple templates and messages — no per-template Meta handle round-trip. Existing assets uploaded via the admin UI are visible here as well.
When to use library_assets vs template_media
Two endpoints, two use cases:
library_assets(this page) — long-lived tenant asset.file_idis reusable across many templates and (in future) free-form messages. Best when the image is part of your brand assets.template_media— one-shot Meta upload handle. The handle only works at template creation time and cannot be re-used. Best when the image is truly single-use or already lives in your own CDN and you don't want to mirror it into SigFollow.
Upload an image
POST
/v1/library_assetsScope: library_assets:write
Request
Body must be multipart/form-data with a single field:
| Name | Type | Required | Description |
|---|---|---|---|
| file | binary | yes | Image file. Image only — accepted MIME types: image/jpeg, image/png, image/gif, image/webp. The file's magic bytes must match the declared Content-Type (HTML / SVG disguised as image/jpeg is rejected at the edge). Max 16 MiB. |
Two safety layers
- MIME whitelist — only
image/(jpeg|png|gif|webp)is accepted. Stricter than the WhatsApp media whitelist on messaging media; non-image uploads are not stored even silently. - Magic-byte verification — the first bytes of the upload must match the declared MIME, so HTML disguised as
image/jpegcan't end up in the library.
Example
curl -X POST https://api.sigfollow.com/v1/library_assets \
-H "Authorization: Bearer sflo_live_xxx" \
-F "file=@/path/to/banner.jpg;type=image/jpeg"Response
{
"file_id": "a1b2c3d4e5f67890abcdef1234567890.png",
"display_name": "banner.png",
"mime_type": "image/png",
"size_bytes": 84123,
"public_url": "https://static.sigfollow.com/images/a1b2c3d4e5f67890abcdef1234567890.png"
}Response fields
| Name | Type | Required | Description |
|---|---|---|---|
| file_id | string | no | Stable library identifier. Pass as header_media_file_id when creating a template via POST /v1/:wabaId/message_templates. Tenant-scoped — keys from other tenants cannot reference this fileId (cross-tenant requests return 403). |
| display_name | string | no | Original filename, deduplicated within the tenant (e.g. logo.png / logo_01.png on collision). Used for the asset's label in the admin Library UI. |
| mime_type | string | no | The declared Content-Type, after MIME whitelist + magic verification. |
| size_bytes | number | no | Stored size in bytes. |
| public_url | string | null | no | Permanent public URL when the platform is configured with a public CDN (PUBLIC_MEDIA_BASE_URL env). Returned null in dev / on-prem deployments without a public origin. Meta uses this URL to fetch the header image at template-send time if the template has a default media. |
List library assets
GET
/v1/library_assetsScope: library_assets:read
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| q | string | no | Optional. Case-insensitive substring match against display_name (PostgreSQL ILIKE). Max 200 characters. |
| page | number | no | Optional. 1-based page index. Default 1. |
| page_size | number | no | Optional. Items per page, 1–100. Default 20. |
| sort | string | no | Optional. One of createdAt-desc (default), createdAt-asc, displayName-asc, displayName-desc. |
Example
curl https://api.sigfollow.com/v1/library_assets?q=banner&page=1&page_size=20 \
-H "Authorization: Bearer sflo_live_xxx"Response
{
"items": [
{
"file_id": "a1b2c3d4e5f67890abcdef1234567890.png",
"display_name": "banner.png",
"mime_type": "image/png",
"size_bytes": 84123,
"created_at": "2026-05-14T10:32:11.000Z",
"public_url": "https://static.sigfollow.com/images/a1b2c3d4e5f67890abcdef1234567890.png"
}
],
"total": 1,
"page": 1,
"page_size": 20
}Use file_id in a template
Once you have a file_id, the cleanest way to build an IMAGE-header template is to pass it via header_media_file_id on the create call — no separate template_media step:
curl -X POST https://api.sigfollow.com/v1/WABA_ID/message_templates \
-H "Authorization: Bearer sflo_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "spring_sale",
"language": "en_US",
"category": "MARKETING",
"header_media_file_id": "a1b2c3d4e5f67890abcdef1234567890.png",
"components": [
{ "type": "HEADER", "format": "IMAGE" },
{ "type": "BODY", "text": "Spring is here — 20% off everything." }
]
}'The backend automatically:
- Verifies the fileId belongs to your tenant (403 otherwise).
- Forks an immutable BUSINESS copy of the image (so deleting the library row later doesn't break the template).
- Uploads the copy to Meta via Resumable Upload and injects the resulting
header_handleinto the first non-TEXT HEADER component'sexample.header_handle.
Explicit header_handle wins
If your
components[].example.header_handle is already populated, that value takes precedence and the header_media_file_idis skipped (no fork, no extra Meta upload) — useful when you cached a handle from a previous run within Meta's TTL.Fails fast if components lacks an IMAGE header
Passing
header_media_file_id on a template whose components array contains no IMAGE / VIDEO / DOCUMENT HEADER returns 400 — the platform refuses to silently drop the image. Add the HEADER component or omit the field.Lifecycle & deletion
- Library assets are tenant-scoped — a key with
library_assets:readsees only its own tenant's library; cross-tenant reference returns 403. - Templates that reference a library asset hold an immutable physical copy (separate BUSINESS object, not a soft link). Deleting the library row in the admin UI does not break templates already created from it.
- Deletion is intentionally not exposed via Public API — references can span templates, default-media URLs, and (future) messages; cleanup needs the confirmation flow in the admin UI to avoid breaking in-flight sends. Manage via the admin Library page.
Errors
400— non-image upload, magic-byte mismatch, or empty file401code190— invalid / revoked API key403code200— missinglibrary_assets:*scope (or cross-tenant fileId when used viaheader_media_file_id)413— file exceeds 16 MiB429code4— rate limit exceeded
See the error reference for body schema.