Media overview¶
TEBHOS serves media from three places. Knowing which one a URL belongs to tells you which query parameters it accepts and which headers it returns.
| Surface | Base URL | Serves |
|---|---|---|
| Media routes | endpoints.media (https://tebhos.com/api/v1) |
Avatars, banners, guild icons and other hash-addressed images, resized on request; any stored object by key through /files/{key} |
| Uploaded-file CDN | https://cdn.tebhos.com |
Stored objects as uploaded: message attachments, custom emoji and sticker images, entrance sounds, group DM icons and other files |
| Media proxy | https://tebhos.com |
Signed external media (/external/...) and OpenGraph metadata (/og) |
Take endpoints.media from instance discovery rather than hard-coding it. URLs to CDN objects are always returned by the API in full (for example an attachment's url), so you never build them yourself.
Route families¶
| Family | Paths | Reference |
|---|---|---|
| Image asset | /avatars, /banners, /server-icons, /server-banners, /server-invite-bg, /server-embed-bg, /live-room-banners under endpoints.media |
Image asset contract |
| Stored object | /files/{key} under endpoints.media |
Get stored object |
| Signed external | /external/{signature}/v2/{target} |
Get signed external media |
| OpenGraph | /og?url= |
Get OpenGraph metadata |
| Signing | GET /media/sign on the HTTP API |
Sign an external URL |
Transformations defines the query parameters that select a size, format and animation. Responses and limits lists statuses, size bounds, deadlines and cache policies.
Not supported on TEBHOS
TEBHOS has no signed attachment URLs (ex, is, hm query parameters), no upload relay, and no separate theme or entrance-sound read routes. Attachments and other uploads are plain CDN URLs; uploads go straight to storage through presigned URLs, see Uploads.
Authorisation¶
No media read route reads the Authorization header. Image assets and stored objects are authorised by their path alone, and signed external media by the signature in its path.
A media URL is a bearer capability
Anyone who has a media URL can fetch the object without an account, and leaving a guild or channel does not revoke a URL you already received. Storage keys and content hashes are long and unguessable, but treat media URLs you did not publish as private.
The only authenticated operation on this surface is signing an external URL, which needs a signed-in user session.
Rate limits¶
Media reads have no request-count rate limit. GET and HEAD requests to the media routes are exempt from the HTTP API global rate limit, and the media proxy has no limiter, so no media response carries rate limit headers.
Methods¶
The media routes under endpoints.media accept GET and HEAD. HEAD returns the same status and headers as GET with an empty body, including Content-Length.
The media proxy routes (/external/... and /og) accept GET only.
Cross-origin reads¶
Every successful media route response under endpoints.media has Access-Control-Allow-Origin: *, whatever Origin the request sent, and its Vary header never contains Origin. Browsers and the desktop app therefore share one cache entry per URL.
The media proxy allows cross-origin GET only from the TEBHOS web app origins (https://tebhos.com and its subdomains). A plain <img> or <video> element does not send Origin and loads from any page.
Byte ranges¶
The media routes under endpoints.media honour a single byte range on the selected representation (after any transformation).
| Value | Description |
|---|---|
bytes={start}-{end} |
Selects the inclusive interval; an end beyond the representation is clamped |
bytes={start}- |
Selects from start through the final byte |
bytes=-{length} |
Selects the final length bytes |
A satisfiable range returns 206 with Content-Range: bytes {start}-{end}/{size}. A reversed range, a zero-length suffix, a start outside the representation, or any range over an empty representation returns 416 with Content-Range: bytes */{size} and an empty body. A value whose unit is not bytes=, or that names more than one range (contains a comma), is ignored and the complete representation is returned.
The media proxy does not forward or honour Range.
Representation headers¶
Successful responses from the image asset and stored object routes carry:
| Header | Description |
|---|---|
| Content-Type | The media type of the returned bytes (detected from the content, not the file name) |
| Content-Length | Length of the returned body, also on HEAD |
| Content-Range? | Present on 206 |
| Accept-Ranges | Always bytes |
| Access-Control-Allow-Origin | Always * |
| Cache-Control | See cache policies |
| CDN-Cache-Control | public, max-age=31536000 |
| Vary | Accept-Encoding, or Accept, Accept-Encoding when the format was negotiated from Accept |
| X-Content-Type-Options | Always nosniff |
| X-Robots-Tag | noindex, nofollow, noimageindex |
| x-tebhos-media-cache | hit, miss or bypass: whether the representation came from the in-memory transformation cache. Informational only |
No media response has a Content-Disposition header. When you save a file, derive its extension from Content-Type.
Content detection¶
The media routes detect an object's type from its first bytes. Only PNG, JPEG, GIF and WebP content is served as an image asset: an object with any other content is reported as 404 on the image asset routes, and the stored object route returns it unchanged with a type derived from its key extension (see Get stored object).
On the media proxy, the origin's Content-Type decides whether the target is proxied at all, see Target validation.