curl --request POST \
--url https://api.paigeme.dev/v1/templates/header-sample \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"source_url": "<string>",
"filename": "<string>",
"mime": "<string>"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({source_url: '<string>', filename: '<string>', mime: '<string>'})
};
fetch('https://api.paigeme.dev/v1/templates/header-sample', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.paigeme.dev/v1/templates/header-sample"
payload = {
"source_url": "<string>",
"filename": "<string>",
"mime": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"success": true,
"data": {
"handle": "<string>",
"format": "<string>",
"filename": "<string>",
"mime_type": "<string>",
"size": 123
}
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}Upload the sample file a media header needs
Meta will not review an IMAGE, VIDEO or DOCUMENT header without an EXAMPLE of the media it will carry, so a media-header template cannot be created until you have one of these handles. Upload the sample here, then put the returned handle into the header component as example.header_handle: ["<handle>"] when you call POST /v1/templates. Creating a media header without it is 400, not a Meta error.
Two transports, pick one.
- JSON
{ "format": "DOCUMENT", "source_url": "https://..." }— Paige downloads it. Only public http(s) URLs: private, loopback and link-local addresses are refused and redirects are re-checked at every hop. Addfilename/mimeto override what the remote server declares. - JSON
{ "format": "IMAGE", "data": "<base64>", "filename": "...", "mime": "..." }— inline bytes, capped at 5 MB decoded, lower than the per-format ceilings because the body is buffered whole in memory. Adata:URI prefix is accepted and stripped.
Size and type are per FORMAT. IMAGE takes image/jpeg or image/png up to 5 MB; VIDEO takes video/mp4 or video/3gpp up to 16 MB; DOCUMENT takes PDF, Word, Excel, PowerPoint or plain text up to 100 MB. Oversize is 413 file_too_large and a type WhatsApp will not take is 415.
The declared mime type is not trusted. The bytes are checked against their own signature, and a file whose contents disagree with its mime is 415 — sending a video as image/png to slip past the image size cap does not work.
A handle is not a media id. media_id from POST /v1/media addresses a file Meta can SEND and is useless as a header_handle; the reverse is also true. The handle is short-lived and single-purpose: get one, create the template with it, and do not store it.
Uploads are metered by BYTES as well as by request count, against the same per-key budget POST /v1/media charges. A burst of large samples can therefore return 429 rate_limited even while you are inside the request-per-minute budget — retry after the window. A source_url download is also aborted mid-stream once it passes that budget, so a sample bigger than a whole minute of it can never be fetched.
Required scope: templates:manage
curl --request POST \
--url https://api.paigeme.dev/v1/templates/header-sample \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"source_url": "<string>",
"filename": "<string>",
"mime": "<string>"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({source_url: '<string>', filename: '<string>', mime: '<string>'})
};
fetch('https://api.paigeme.dev/v1/templates/header-sample', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.paigeme.dev/v1/templates/header-sample"
payload = {
"source_url": "<string>",
"filename": "<string>",
"mime": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"success": true,
"data": {
"handle": "<string>",
"format": "<string>",
"filename": "<string>",
"mime_type": "<string>",
"size": 123
}
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}Authorizations
Project API key issued in Settings → API keys. Send it as Authorization: Bearer pk_live_….
Headers
Target project id, for an MCP OAuth bearer (mcp_at_…) attached to more than one project — get ids from GET /v1/projects. Matched case-insensitively.
Omit it and a READ falls back to the connection's default project; a mutation (any non-GET) on a connection with 2+ projects is rejected with 400 project_required — a write is never defaulted to a guessed project. A connection with exactly one project never needs the header.
Scopes are checked against the SELECTED project only, never a union across the connection.
For a pk_ API key the header selects nothing — one key is one project's context — but it IS validated: omit it and the key's own project is used, send it and it must name that project, otherwise the call is rejected with 403 project_not_attached (a blank value is 400 invalid_project_header, as above).
Body
- Option 1
- Option 2
The header format this sample is for. IMAGE takes JPEG/PNG up to 5 MB, VIDEO takes MP4/3GP up to 16 MB, DOCUMENT takes PDF/Office/plain text up to 100 MB. The declared type is checked against the file own magic bytes, so a mislabelled file is refused here rather than forwarded to Meta.
IMAGE, VIDEO, DOCUMENT Public http(s) URL Paige downloads the sample from. Private, loopback and link-local addresses are refused and redirects are re-checked at every hop. The download is aborted mid-stream at the smaller of this format ceiling and the API key remaining per-minute upload byte budget, so a sample larger than that budget can never be fetched.
Overrides the filename derived from the URL.
1 - 255Overrides the Content-Type the remote server declares. Use it when the host serves the file as application/octet-stream.
1 - 255Response
Success.
true Hide child attributes
Hide child attributes
Meta's opaque upload handle. Goes straight into the HEADER component as example.header_handle: [handle].
IMAGE | VIDEO | DOCUMENT — the format the sample was validated against.
The mime type Paige verified against the bytes, not necessarily the one you declared.
Decoded size in bytes.
Was this page helpful?
