Integration Patterns
How a typical integration looks end to end, which pattern fits which kind of platform, and the practices that keep an integration robust.
Worked example - Headless CMS integration
A custom field (or "app", "extension") in a headless CMS lets editors pick a Mediaflow asset and stores the resulting CDN URL on a CMS entry. Walked through here for Contentful; the same shape applies to Strapi, Sanity, Storyblok, Hygraph, and most headless CMSes.
What the editor sees
Opens a CMS entry. The "hero image" field is a Mediaflow asset field.
Clicks Browse Mediaflow → a Mediaflow asset picker opens (web component or a custom UI).
Editor picks an image; the picker returns the numeric file id, and the extension resolves
{imageId, filename, filetype}viaGET /1/file/{id}?fields=mediaId,filename,type(serverIdcomes from the auth token).The CMS field saves
{resourceId, url}(plus the picker data for display).The published page renders
<img src="{url}?w=1600&f=auto">- served by the Mediaflow CDN, not by the CMS.
What the integration extension does
Editor picks an asset (numeric file id)
│
▼
GET /1/file/{id}?fields=mediaId,filename,type → imageId, filename, filetype (type.id)
│
▼
POST /1/cdn/resources ← server-side dedup on the natural key
│
├─ 200 OK - match on a completed resource (reused)
└─ 202 Accepted - new or re-queued
│
▼
Save {resourceId, url} on the CMS field
│
▼
(optional) poll GET /1/cdn/resources/{resourceId} until status=completed
│
▼
Show preview / ready state to the editorWhat does NOT need to change in the CMS
Published page URLs - the CDN URL is stable across
PUT/PATCH.Image transforms per breakpoint - those live in the render layer, not on the CMS field. The CMS stores the canonical URL and defaults; render code appends
?w=…&f=autoper breakpoint.
Common integration patterns
Partner type | What's stored on the partner platform | Where transforms are applied |
|---|---|---|
Headless CMS (Contentful, Strapi, Sanity, Storyblok, …) | Custom field per asset: | Render layer / template ( |
Traditional CMS (WordPress, Drupal, …) | Block attribute, shortcode, or media-library entry holding the URL | Theme / template |
E-commerce (Shopify, Centra, …) | Product / variant image attribute | Storefront theme |
Native apps (iOS / Android) | Asset URL on the app's content backend | Client image loader (request the right variant URL) |
Newsletter / static-site builder | URL in the template or builder field | Build step, or query params at send time |
The integration manages the resource lifecycle. The partner platform stores the stable URL. The render layer appends transform parameters per usage.
Best practices
Re-POST is safe.
POSTis idempotent on(customer, imageId, serverId, cdnId, filename). A resource left atregisteredby a prior503is picked up and re-published by the next call - no manual dedup needed.Store both
urlandresourceId. URL → embed; resourceId → lifecycle calls.Treat the URL as immutable. It doesn't change across
PUT/PATCH- store it once.Keep
defaultssmall. Defaults are the fallback applied when no transform param is sent. Per-breakpoint sizes (srcset entries) belong in the render layer as query/path parameters, not in defaults.Let the edge negotiate format. Leave
funset orautoand the edge picks AVIF, then WebP, from the request'sAcceptheader, and caches each format separately, so every client gets a format it accepts. Negotiation only runs when the request is transformed: a resource withoutdefaults, fetched with no parameters, is served as its stored rendition (WebP for images). GIF sources fall back to PNG for clients that accept neither, see Supported file types.The filename in the URL does not choose the output format.
hero.jpgwith no parameters returns the stored WebP rendition; setf=jpegwhen you need JPEG bytes.Soft-delete on partner-side delete. Skipping
DELETEleaves orphan resources behind.Always use
GET.HEADis not served (405).