# Video, audio and images

> How video, audio and image files are uploaded, processed and stored, the length each plan allows, and who can open a file before a testimonial is approved.

A testimonial can carry a video or audio recording and up to 4 images.
A person can have a photo, and a site can have a logo. This page covers
how those files are uploaded, what happens to them, who can open them
and what deleting one does. Files are added where they are used: on a
[form](/docs/testimonials/forms), when you [add a testimonial by
hand](/docs/testimonials/add-by-hand), on a person and in the [brand
kit](/docs/testimonials/brand-kit).

## Limits

**Video and audio by plan.** Free takes text testimonials only. Pro
takes recordings up to 2 minutes, Business up to 10 minutes.

| Kind | Largest file | Formats |
|---|---|---|
| Video | 500 MB | MP4, MOV, WebM, MKV, M4V, OGG, 3GP, MPEG, AVI |
| Audio | 500 MB | MP3, M4A, AAC, WAV, WebM, OGG, FLAC |
| Image | 10 MB | JPEG, PNG, WebP, GIF |
| Logo | 2 MB | JPEG, PNG, WebP, GIF, SVG |

The 500 MB limit is a safety limit and is the same on every plan.

What each plan sees:

- **A form on Free does not offer the video or audio answer.** The
  customer sees the written answer only and never a message about your
  plan.
- In the app on Free, a video or audio upload field is shown locked,
  with "Video and audio testimonials are part of the Pro plan." and a
  link to the plans.
- **A recording longer than your plan takes is refused before it is
  sent**, in the form and in the app, with the length your plan takes.
  The recorder on a form stops by itself at the limit.
- The length is measured again when the file is processed. A file that
  is too long fails with "This recording is longer than the site's plan
  takes. Trim it and upload it again." It is never cut short.
- **Recordings already stored keep playing after a downgrade.** Nothing
  is deleted or hidden when a plan changes.

## The upload field

An upload field in the app has **Choose a file** and a line with the
limit, for example "Up to 500 MB and 2 minutes." After you choose a
file it shows **Uploading**, then **Processing**, then **Uploaded**.
**Remove** takes the file off the field.

## What happens to a file

1. **Upload.** The browser sends the file straight to a private upload
   store, through a link that works for that one file, its type and its
   exact size, for 2 hours. A file over 16 MB can be sent in parts of 8
   MB, and a part that fails is sent again on its own. Nothing in the
   upload store is public, and no page ever reads from it.
2. **Processing.** The file is turned into what plays everywhere:
   - a video becomes one MP4, at most 1,280 pixels on its long side,
     with a still picture,
   - an audio file becomes one MP3,
   - an image becomes a cleaned JPEG or PNG, at most 2,000 pixels on
     its long side, with two smaller sizes (960 and 320 pixels),
   - an SVG logo is cleaned of scripts.

   Place, device and time that a camera wrote into the file are
   removed.
3. **Publishing.** The finished files are written to the media host.
   Then the file as it was uploaded is deleted from the private store.

Nothing is public as it was sent: only the processed files reach the
media host.

A file is shown, to you as well, only once it is processed. Until then
the page shows **Processing**, which usually takes a minute.

If a file cannot be processed it is marked **Failed** with the reason.
**Retry** tries again while the upload is still in the private store,
which is 24 hours after the failure. After that the file has to be
uploaded again.

**An upload that is not processed is deleted after 24 hours.** That
covers an upload that was started and never finished, and the upload of
a file that failed. A second check once a day deletes anything in the
private store that is older than 48 hours and that no file record
names.

## A file's page

Each file has a page of its own in the app, with its player or picture
and the details below. Open it from a testimonial's page: under a
recording, beside its state and size, is **File details**. The page
leads back to the testimonial.

| Detail | What it shows |
|---|---|
| Badge | Uploading, Processing, Ready or Failed |
| Kind | Image, Video or Audio |
| Size, Length, Dimensions | Of the processed file |
| Where it is shown | "In widgets, on walls and on public pages. Anyone with the address of the file can open it." or "Only to members of this site, until its testimonial is approved. The address of the file is not secret, so do not pass it on." |
| Added | The date |

**Retry** shows for a failed file, and **Delete file** for a file that
no testimonial uses. Both need the `content.write` permission.

## Who can open a file

**Every processed file has one lasting address on the media host. The
address is the same for everyone and does not depend on the
testimonial's status. Anyone who has the address can open the file.**

There is no login in front of a file and the address does not expire.
The address ends in 32 random characters, so it cannot be guessed, and
the media host cannot be listed. It does not change when a testimonial
is approved, rejected or moved to the trash.

So for a testimonial that is **not approved yet** (pending or
rejected):

- Its files can be opened by anyone who has their addresses.
- Privatus Analytics gives those addresses only to members of the site:
  in the app, and over the API and MCP to a caller with access to the
  site.
- It never puts them in a widget, a wall, a public page of a
  testimonial, the public JSON of a widget, a sitemap or an email to
  someone outside your workspace.
- If a member copies the address of such a file and passes it on,
  whoever gets it can open the file. Treat the address like the file
  itself.

Once a testimonial is **approved**, its files are shown in your
widgets, on your walls and on its public page, at the same addresses.
A site's logo and a wall's share image are public from the start.

> **Privacy:** Unapproving a testimonial or moving it to the trash
> takes its files out of widgets, walls and public pages. It does not
> change or close their addresses. Only deleting the file does.

## Deleting a file

| What you do | What happens to the files |
|---|---|
| Delete a testimonial for good, from the [trash](/docs/testimonials/trash) or when its 30 days there are over | Its files are deleted |
| **Delete file** on the page of a file no testimonial uses (a logo you replaced, a photo, an upload you never used) | The file is deleted. A logo or photo that pointed at it is cleared |
| Nothing | A file that nothing uses is deleted by itself a week after it was uploaded |
| Delete the site | Every file of the site is deleted |

A file that belongs to a testimonial cannot be deleted on its own. It
goes with the testimonial.

Deleting a file does two things, one after the other:

1. The stored file is deleted from the media host.
2. Its address is purged at Cloudflare, so the copy kept there is
   cleared and the address stops working.

**How long a deleted file can still be cached.** Files are served with
a cache lifetime of one year, because a file never changes at its
address. The purge clears Cloudflare's copy when the file is deleted.
It cannot reach a copy that a browser already saved: a browser that
loaded the file before can keep showing its own copy for up to a year.

## API and MCP

The inputs and the response fields are in the [API
reference](/docs/api) and the [MCP reference](/docs/mcp).

| Action | MCP tool | Permission |
|---|---|---|
| Start an upload, or add a file from an address | `testimonial_media_uploads_create` | `content.write` |
| Finish an upload | `testimonial_media_uploads_finish` | `content.write` |
| Read one file | `testimonial_media_get` | `analytics.read` |
| Try a failed file again | `testimonial_media_retry` | `content.write` |
| Delete a file nothing uses | `testimonial_media_delete` | `content.write` |

To upload a file, call `testimonial_media_uploads_create`, send the
file to the address in `upload` with exactly the headers it names, call
`testimonial_media_uploads_finish`, then read the file with
`testimonial_media_get` until `state` is `ready`. `url` is null until
then. A caller with no browser can send `source_url` instead: the
server downloads the file from that public http or https address and
processes it like an upload.

`shareable` in the response is true when the address may be given to
people outside the site (a file of an approved testimonial, a logo, a
share image). The address itself is the same either way.

A video or audio file on a plan that takes text only responds with
`402 plan_limit`. Other refusals are a 422 with a code: `too_large`,
`unsupported_type`, `not_uploaded`, `unreadable` (the first bytes are
plainly not the kind of file promised), `wrong_state`, `not_retryable`,
`unsafe_url`, `in_use`, `storage_unavailable`. A file that failed while
processing carries its reason in `error`.

Deleting a file cannot be undone, so the MCP tool
`testimonial_media_delete` runs nothing and points to the file's page
in the app. The API call runs as usual.

## Good to know

- An image a customer attaches on a form is one of up to 3. A
  testimonial holds up to 4 images.
- A recording that an [import](/docs/testimonials/imports) brings in
  from another site is held to 10 minutes on every plan, not to the
  plan's length.
- A file a customer uploaded on a form they never sent is deleted about
  a week later, with the rest of that unfinished answer.
- An image of more than 60 million pixels cannot be processed.
