SigFollow
Sign in

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_id is 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_assets

Scope: library_assets:write

Request

Body must be multipart/form-data with a single field:

NameTypeRequiredDescription
filebinaryyesImage 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
  1. 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.
  2. 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
NameTypeRequiredDescription
file_idstringnoStable 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_namestringnoOriginal 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_typestringnoThe declared Content-Type, after MIME whitelist + magic verification.
size_bytesnumbernoStored size in bytes.
public_urlstring | nullnoPermanent 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_assets

Scope: library_assets:read

Query parameters

NameTypeRequiredDescription
qstringnoOptional. Case-insensitive substring match against display_name (PostgreSQL ILIKE). Max 200 characters.
pagenumbernoOptional. 1-based page index. Default 1.
page_sizenumbernoOptional. Items per page, 1–100. Default 20.
sortstringnoOptional. 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:

  1. Verifies the fileId belongs to your tenant (403 otherwise).
  2. Forks an immutable BUSINESS copy of the image (so deleting the library row later doesn't break the template).
  3. Uploads the copy to Meta via Resumable Upload and injects the resulting header_handleinto the first non-TEXT HEADER component's example.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 file
  • 401 code 190 — invalid / revoked API key
  • 403 code 200 — missing library_assets:* scope (or cross-tenant fileId when used via header_media_file_id)
  • 413 — file exceeds 16 MiB
  • 429 code 4 — rate limit exceeded

See the error reference for body schema.