Attachment API
Contents
The Attachment API provides direct access to Content Attachments without processing.
Location
By default, the API is available at:
8080:/api/media:attachment/
The exact public URL can differ per environment based on virtual host configuration, which is commonly used to expose the API on a friendlier path — for example https://api.example.com/graphql.
Attachment URL structure
The attachment URL consists of the following components:
-
Base
:8080/api/media:attachment/ -
Media Locator with an optional (branch)
<project>(:<branch>)/<content-id>
masteris the default branch (referring to published content). Specifyingmasterbranch is an error - to avoid cache pollution. Since we only havemasteranddraftthere are only two options:/<project>/and/<project>:draft/ -
(Optional) fingerprint
:<attachment-fingerprint>- the fingerprint of the attachment.
If fingerprint is not supplied the response will not contain "immutable"Cache-Controlheader. -
Attachment name
<attachment-name>is not the Content Name, but the name of the attachment stored within the content. -
(Optional) download parameter
?download- the response will set theContent-Dispositionheader, if specified.
Example of an attachment URL on the root of a live site behind a Virtual Host:
/_/media:attachment/media-project/c82262a6-77d6-4276-b0e7-fe82f4eb57b8:ec25d6e4126c7064f82aaab8b34693fc/attachment.png?download
Media scope
You may limit which projects and branches are accessible by mounting the API in a Virtual Host context.
Use the setting endpoint.media.scope = <project1:draft>, <project2> in the Virtual Host Context.
Response
A successful response streams the attachment binary in the body. The API accepts GET, HEAD, and OPTIONS — any other method returns 405 Method Not Allowed.
Headers
-
Content-Type -
Set to the attachment’s MIME type. SVG attachments (
.svg,.svgz) are always served asimage/svg+xml. -
Content-Length -
Size of the attarchment, in bytes.
-
Accept-Ranges -
Always set to
bytes, signalling that the API supports partial downloads via theRangerequest header (see Range requests). -
Cache-Control -
Set only when the URL includes a fingerprint that matches the current binary hash. The value is taken from
media.public.cacheControlfor content publicly readable on themasterbranch, and frommedia.private.cacheControlotherwise. Requests without a fingerprint, or with a stale one, receive noCache-Controlheader and are therefore treated as non-cacheable by intermediaries. -
Content-Security-Policy -
Set when configured via the
media.contentSecurityPolicyportal config option. SVG responses use the separatemedia.contentSecurityPolicy.svgsetting, which lets you tighten policy specifically for the inline-script-capable image format. -
Content-Disposition -
Set to
attachment; filename="<name>"only when the?downloadquery parameter is present, prompting browsers to save the file rather than render it inline. -
Content-Encoding -
The platform-wide gzip filter is disabled by default (
gzip.enabled = false), so attachments are served uncompressed unless an operator opts in. This default keeps the API edge-cache and CDN friendly — so that services like Fastly can perform their own conditional compression and chunking without conflicting with an upstreamContent-Encoding.
Range requests
The API honours Range: bytes=… request headers, allowing clients to resume interrupted downloads or stream large media progressively.
-
Single range →
206 Partial ContentwithContent-Range: bytes <start>-<end>/<total>and a body containing the requested slice. -
Multiple ranges →
206 Partial ContentwithContent-Type: multipart/byteranges; boundary=…; the body is a multipart payload with each range as a separate part. -
Out-of-bounds range →
416 Requested Range Not SatisfiablewithContent-Range: bytes */<total>and an empty body.
GET /api/media:attachment/myproject/abc123:fp/large-video.mp4 HTTP/1.1
Range: bytes=0-1048575
HTTP/1.1 206 Partial Content
Content-Type: video/mp4
Content-Length: 1048576
Content-Range: bytes 0-1048575/52428800
Accept-Ranges: bytes
Configuration
The Attachment API is driven by two configuration files in your XP installation.
- CMS configuration
-
Controls media-serving behaviour:
media.public.cacheControl,media.private.cacheControl,media.contentSecurityPolicy,media.contentSecurityPolicy.svg, and the per-virtual-hostendpoint.media.scopeattribute. - System configuration
-
Controls platform-wide HTTP behaviour, including the Jetty gzip filter (
gzip.enabled,gzip.minSize) referenced in the headers section above.