curl --request GET \
--url https://api.paigeme.dev/v1/media/{slug} \
--header 'Authorization: Bearer <token>'const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.paigeme.dev/v1/media/{slug}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.paigeme.dev/v1/media/{slug}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text){
"success": true,
"data": {
"asset": {
"slug": "<string>",
"media_id": "<string>",
"meta_status": "registered",
"meta_uploaded_at": "<string>",
"mime": "<string>",
"bytes": 123,
"filename": "<string>",
"kind": "image",
"caption": "<string>",
"source": "chat_upload",
"created_at": "<string>"
},
"url": "<string>",
"expires_in": 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>"
}Retrieve one media asset and a signed URL
The asset plus a short-lived signed url for its bytes. The URL is unauthenticated once minted, so treat it as a secret and let it expire — expires_in is capped, deliberately, so a scoped read cannot be turned into a permanent public link.
download=true signs it with a download disposition (the browser saves the file) instead of an inline one.
An unknown slug is 404 ASSET_NOT_FOUND. So is a slug that exists but belongs to inbound customer media when the key lacks conversations:read — identical response either way, so probing slugs reveals nothing about what a customer sent in.
Required scope: media:read
curl --request GET \
--url https://api.paigeme.dev/v1/media/{slug} \
--header 'Authorization: Bearer <token>'const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.paigeme.dev/v1/media/{slug}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.paigeme.dev/v1/media/{slug}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text){
"success": true,
"data": {
"asset": {
"slug": "<string>",
"media_id": "<string>",
"meta_status": "registered",
"meta_uploaded_at": "<string>",
"mime": "<string>",
"bytes": 123,
"filename": "<string>",
"kind": "image",
"caption": "<string>",
"source": "chat_upload",
"created_at": "<string>"
},
"url": "<string>",
"expires_in": 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>"
}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).
Path Parameters
The asset's bot-facing slug — the key the runtime getMediaId / sendMedia helpers look it up by.
^[a-zA-Z0-9][a-zA-Z0-9-]{0,119}$Query Parameters
Sign the URL with a download disposition instead of an inline one.
true, false, 1, 0, Signed-URL lifetime in seconds (30-3600, default 300).
30 <= x <= 3600Response
Success.
true Hide child attributes
Hide child attributes
Hide child attributes
Hide child attributes
The bot-facing name. sendMedia(to, "<slug>") and getMediaId("<slug>") resolve by this.
Meta's id for the registered copy, used when the bot sends the file. Null when registration did not happen (usually no WhatsApp number connected) — the asset is still stored and Paige retries on a schedule.
registered — ready to send. pending — stored but not registered with Meta yet. expired — the id passed Meta's 30-day lifetime and is being refreshed.
registered, pending, expired When the file was registered with Meta (ISO 8601), or null.
Stored mime type, e.g. image/png.
Size of the stored original.
The original filename it was uploaded under.
What Paige did with it. knowledge means it was text-extracted into the bot's knowledge base (PDF, DOCX, text, markdown).
image, video, audio, knowledge The caption supplied at upload, if any.
chat_upload is your own upload. conversation_inbound is a file a customer sent in over WhatsApp — only ever returned to a key that also holds conversations:read.
chat_upload, conversation_inbound When the asset was created (ISO 8601).
Signed URL for the bytes, or null when the asset has no stored object.
Seconds the signed URL stays valid.
Was this page helpful?
