Media
Post pictures, voice notes, video, audio and GIFs, play them back, show avatars, and render link previews safely. Includes the units and size limits.
Media goes through the same scans, size limits and moderation as in the UNET apps. This page covers sending media with a post, getting it back for display, and the link preview line that posts can carry. Posting needs posts:write; reading media needs content:read. See Permissions.
Posting media
createPost() sends JSON for a text post and switches to a multipart upload as soon as there is a file. Pass files as Blob (a File from an <input type="file"> is one).
Pictures
Up to ten images per post (JPEG, PNG, WebP, GIF, HEIC):
declare const files: File[];
await unet.api.createPost({ content: "Trip", images: files.slice(0, 10) });Voice notes and video
One voice note or video per post, passed as voice. The duration is required, and it is in milliseconds:
declare const recording: Blob;
await unet.api.createPost({
content: "Listen",
voice: recording, // MP3, M4A, OGG, WebM, AAC, WAV (a voice note) or MP4, MOV (a video)
voiceDuration: 12_000, // a voice note: 12 seconds, in milliseconds
});
declare const clip: Blob;
await unet.api.createPost({
voice: clip,
videoDuration: 45_000, // a video: 45 seconds, in milliseconds
});voiceWaveform takes up to 200 amplitude values for the player's wave drawing.
Audio
An audio post stands alone (no images or voice), up to 10 MB. Its duration is in seconds:
declare const track: Blob;
await unet.api.createPost({ content: "New track", audio: track, audioDuration: 183 });GIFs
The GIF picker uses the same provider as the UNET apps. Pick one, then attach its address:
const { gifs } = await unet.api.gifs.search("cat"); // or gifs.trending()
await unet.api.createPost({ content: "meow", gifUrl: gifs[0]!.gifUrl });previewUrl of each GIF is the small thumbnail for your picker.
Limits
| What | Limit |
|---|---|
| Images in a post | 10 |
| Avatar, banner, a comment picture | 10 MB each |
| An audio post | 10 MB |
| One request, files included | 250 MB; more fails with ApiError 413, code: "payload_too_large" |
| Posts | 10 per 10 minutes, 100 per day per person and app |
File uploads have no client-side timeout. Pass a signal with unet.api.withSignal() to let the person cancel; see Call the UNET API.
Profile pictures
declare const photo: File;
const { avatarUrl } = await unet.api.uploadAvatar(photo); // JPEG, PNG, WebP, GIF or HEIC, up to 10 MB
await unet.api.uploadBanner(photo);These need profile:write. The server scans and resizes the file like the apps do. To show a person's avatar, use avatarUrl (on the user, on authors and on profiles). It is a ready URL; the older avatar claim is a storage key and not an address.
Showing media
Pictures are public addresses. Use them directly:
const post = await unet.api.post(123);
for (const image of post.images) {
const img = document.createElement("img");
img.src = image.thumbUrl ?? image.url; // also mediumUrl for a middle size
img.alt = image.altText ?? "";
document.body.append(img);
}Video and audio files are not public: a <video src> or <audio src> cannot send the access token. The SDK has two ways:
| Call | Use for | How it works |
|---|---|---|
mediaLink(url) | video | Returns a signed link (about 30 minutes, see expiresAt) that a <video src> can stream from directly: playback starts at once and seeking works. |
media(url) | audio, voice notes, stickers | Downloads the file with your token as a Blob. Play it from a blob URL. |
const post = await unet.api.post(123);
if (post.video) {
const { url } = await unet.api.mediaLink(post.video.url);
document.querySelector("video")!.src = url;
}
if (post.audioUrl) {
const blob = await unet.api.media(post.audioUrl);
const audio = document.querySelector("audio")!;
audio.src = URL.createObjectURL(blob);
audio.onended = () => URL.revokeObjectURL(audio.src); // release the memory
}The SDK only ever sends your token to UNET's own API, never to the host in the URL you pass, and refuses anything that is not a UNET media address with ApiError code: "not_media_url". Ask for a new mediaLink when the old one has expired. Content security: allow blob: in media-src for the media() approach.
Link previews in post text
When someone shares a link, the apps append a preview to the post text as an invisible last line. post.content therefore contains it. Split it off before you show the text:
import { splitLinkPreview } from "@unet/id-web";
const post = await unet.api.post(123);
const { text, preview } = splitLinkPreview(post.content);
console.log(text); // what to show: no preview line, no invisible markers
if (preview) {
console.log(preview.url, preview.title, preview.description, preview.image);
}splitLinkPreview returns url and image only when they are http or https addresses; a preview with any other kind of address (for example javascript:) is dropped, and the text still shows. This protects an app that puts preview.url into a link. title and description are plain text written by the poster: put them in with textContent, never as HTML.
When editing a post, use body (the text without the preview line but with the markers that switch previews off) and put the preview back with withLinkPreview(body, preview).
Common mistakes
- Wrong duration unit. Milliseconds for voice and video, seconds for audio.
- Using
<video src="post.video.url">. The address needs a token; usemediaLink(). - Forgetting
URL.revokeObjectURL(). Blob URLs hold memory until released. - Inserting preview text as HTML. Treat all of it as text.
- Uploading in a loop. Posts are limited to 10 per 10 minutes.