Public endpoints
The visitor-facing endpoints a published profile exposes - rendered page, Open Graph card, live Discord presence, the SSE stream, and the embedded music proxy.
Public endpoints
A published profile exposes a handful of unauthenticated endpoints to visitors and their browsers. None of them take an API key. Access is governed entirely by the profile row: it must be published (is_public), not suspended, and not expired. Contact-form POSTs are documented separately on Contact forms.
The rendered profile page
https://<slug>.hep.gg/PublicThe profile site itself. It is host-routed, not path-routed: the API matches the request's Host header against profiles.domain (case-insensitive) and renders the matching published profile. The same *.hep.gg subdomain can serve a profile at / and uploader files at /<shortid> in parallel.
- No body, no query parameters. The page is driven entirely by the
Hostheader. - Returns
200withContent-Type: text/html; charset=utf-8andCache-Control: no-store. - Each render records a view (per IP and User-Agent, with a naive 30-second dedup to keep refresh spam from inflating counts). The optional
RefererandUser-Agentheaders are captured. - The page head includes Open Graph and Twitter card tags;
og:imageandtwitter:imagepoint at the OG image endpoint, andtwitter:cardissummary_large_image. - If no published profile matches the host, the request falls through (404, or a redirect to hep.gg for unmatched custom domains). The
hep.ggapex and other exempt root domains always fall through to the dashboard.
Premium-gated features degrade gracefully for non-premium owners: the Discord presence card is hidden and the "Powered by Hep.gg" footer is forced visible. See visibility and availability.
Open Graph image
https://hep.gg/api/v1/profiles/:profileID/og.pngPublicEvery public profile auto-exposes a social card as a PNG. The rendered page references this URL as its og:image, so Discord, Twitter/X, Facebook, and other crawlers fetch it when someone shares the profile link. Presence and activity cards and the powered-by footer are forced off in this render so the image stays static.
profileIDprofiles.id).- Returns
200withContent-Type: image/pngandCache-Control: public, max-age=300. - Returns
404with bodyNot foundif the profile is missing, suspended, or not public. - Returns
500with bodyRender failedif image rendering errors.
curl -o card.png https://hep.gg/api/v1/profiles/PROFILE_ID/og.pngconst res = await fetch("https://hep.gg/api/v1/profiles/PROFILE_ID/og.png");
const png = Buffer.from(await res.arrayBuffer());Live Discord presence (snapshot)
https://hep.gg/api/v1/profiles/presence/:userIDPublicReturns the profile owner's current Discord presence. Backed by a Redis cache with a 5-minute TTL, populated by the Hep.gg Discord bot's presence listener. The owner must have Discord presence sharing enabled on the bot for this to return anything.
userIDReturns 200 with the standard envelope. data.presence is a PresenceSnapshot object, or null when the cache is empty or expired.
{
"ok": true,
"data": {
"presence": {
"status": "online",
"activity": {
"name": "Spotify",
"type": 2,
"details": "Song Title",
"state": "Artist Name",
"startedAt": 1716900000000,
"endsAt": 1716900210000,
"largeImage": "https://i.scdn.co/image/...",
"largeText": "Album Name"
},
"updatedAt": "2026-05-29T12:00:00.000Z"
}
}
}Presence snapshot
statusonline, idle, dnd, offline.activityupdatedAtnameSpotify, Visual Studio Code.type0 Playing, 1 Streaming, 2 Listening, 3 Watching, 4 Custom (text is in state), 5 Competing.detailsstatestartedAtendsAtlargeImagelargeTextLive Discord presence (SSE stream)
https://hep.gg/api/v1/profiles/presence/:userID/streamPublicA Server-Sent Events stream so a profile page can update its presence card live without polling. The server sends an initial snapshot on connect, then pushes a new event whenever the owner's presence changes, plus a keepalive comment every 30 seconds.
userID- Returns
200withContent-Type: text/event-stream,Cache-Control: no-store,Connection: keep-alive. - Returns
400with{ "ok": false, "error": "userID required" }ifuserIDis missing. - Each presence event is
event: presencefollowed by adata:line containing aPresenceSnapshotJSON object. - Keepalive pings are SSE comment lines (
: ping) sent every 30 seconds.
event: presence
data: {"status":"online","activity":{"name":"Spotify","type":2},"updatedAt":"2026-05-29T12:00:00.000Z"}
: ping
event: presence
data: {"status":"idle","updatedAt":"2026-05-29T12:01:30.000Z"}curl -N https://hep.gg/api/v1/profiles/presence/USER_ID/stream// Browser EventSource; works the same in any SSE client.
const es = new EventSource("https://hep.gg/api/v1/profiles/presence/USER_ID/stream");
es.addEventListener("presence", (e) => {
const snap = JSON.parse(e.data);
console.log(snap.status, snap.activity?.name);
});Embedded music proxy
https://hep.gg/api/v1/profiles/:profileID/music/:isrcPublicThe audio source for music embedded on a public profile. The rendered <audio> tag points here and the browser hits it anonymously. Server-side, Hep.gg uses the profile owner's music key to fetch the audio. Plays count against the owner's music stats, and revoking the owner's music access cleanly disables profile audio.
profileIDprofiles.id).isrc^[A-Z0-9]{12}$ (case-insensitive).GETandHEADare both supported.- The upstream worker's response is streamed back with its status passed through. A
Rangerequest header is forwarded best-effort (the music worker does not currently honorRange). - These upstream headers are forwarded to the browser:
content-type,content-length,content-range,accept-ranges,last-modified,etag,x-track-title,x-track-artist,x-track-duration,x-track-album,x-track-artwork. - Returns
400(empty body) on a missingprofileIDor an ISRC that fails the pattern. - Returns
404(empty body) if the owner has no enabled music key (or the profile is not public/suspended/expired). - Returns
502(empty body) on upstream failure.
curl -o track.mp3 https://hep.gg/api/v1/profiles/PROFILE_ID/music/USRC17607839<audio controls src="https://hep.gg/api/v1/profiles/PROFILE_ID/music/USRC17607839"></audio>