# Check and optimize a video

Send a video to a check or an optimization, follow its job, then read each frame's reading or download the optimized MP4.

## How a video runs

A video is a job on both endpoints. `POST /audit` and `POST /optimize` answer 202 at once with its `id` and `media: "video"`, and you poll until its `status` is `succeeded` or `failed`.

| Call             | Poll it with                                                 |
| ---------------- | ------------------------------------------------------------ |
| Check a video    | [`GET /audit/{id}`](/docs/api-reference/get-check)           |
| Optimize a video | [`GET /optimize/{id}`](/docs/api-reference/get-optimization) |

The statuses, the polling rhythm and the job's lifetime are an optimization's, described in [Optimize an image](/docs/guides/optimize-an-image#how-an-optimization-runs). A video optimization also takes `callback_url` for a [signed webhook](/docs/guides/optimize-an-image#get-a-webhook-instead).

Send exactly one of `video_url` or `video_upload_id`. A video on disk goes through [an upload](/docs/guides/upload-a-local-file) with its video `content_type`. A large video is faster that way than by URL.

## Send the length

Pass `seconds`, the video's length, when you know it. Credits are held for that length and a video that runs longer is refused. Without it, credits for the longest video are held, and the real length is charged once the job finishes. [Video limits](/docs/credits-and-limits#video-limits) has the numbers.

## Check a video

**Check a video by URL**

```bash title="cURL"
curl -X POST "https://realpng.com/api/v1/audit" \
  -H "X-API-Key: $REALPNG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "video_url": "https://videos.example.com/drone.mp4",
    "seconds": 12
  }'
```

```javascript title="JavaScript"
const res = await fetch("https://realpng.com/api/v1/audit", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.REALPNG_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "video_url": "https://videos.example.com/drone.mp4",
    "seconds": 12
  }),
});
const data = await res.json();
```

```python title="Python"
import os

import requests

res = requests.post(
    "https://realpng.com/api/v1/audit",
    headers={"X-API-Key": os.environ["REALPNG_API_KEY"]},
    json={
        "video_url": "https://videos.example.com/drone.mp4",
        "seconds": 12,
    },
)
data = res.json()
```

Response `202 Accepted`

```json
{
  "id": "b8f2d6a4-3c1e-4f7a-9d5b-2e6c8a0f4b13",
  "status": "queued",
  "media": "video"
}
```

Then poll the check until it finishes.

**Get the video's check**

```bash title="cURL"
curl "https://realpng.com/api/v1/audit/b8f2d6a4-3c1e-4f7a-9d5b-2e6c8a0f4b13" \
  -H "X-API-Key: $REALPNG_API_KEY"
```

```javascript title="JavaScript"
const res = await fetch("https://realpng.com/api/v1/audit/b8f2d6a4-3c1e-4f7a-9d5b-2e6c8a0f4b13", {
  headers: {
    "X-API-Key": process.env.REALPNG_API_KEY,
  },
});
const data = await res.json();
```

```python title="Python"
import os

import requests

res = requests.get(
    "https://realpng.com/api/v1/audit/b8f2d6a4-3c1e-4f7a-9d5b-2e6c8a0f4b13",
    headers={"X-API-Key": os.environ["REALPNG_API_KEY"]},
)
data = res.json()
```

Response `200 OK`

```json
{
  "id": "b8f2d6a4-3c1e-4f7a-9d5b-2e6c8a0f4b13",
  "status": "succeeded",
  "verdict": "ai",
  "score": 88,
  "decided_by": "pixels",
  "lanes": {
    "pixels": {
      "verdict": "ai",
      "score": 88
    },
    "watermark": {
      "verdict": "none",
      "score": 4
    },
    "provenance": {
      "verdict": "nothing",
      "issuer": null
    }
  },
  "media": "video",
  "video": {
    "seconds": 12,
    "fps": 30,
    "frames": 360,
    "width": 1280,
    "height": 720,
    "audio": "copied",
    "readings": [
      {
        "t": 0.75,
        "verdict": "ai",
        "score": 84
      },
      {
        "t": 2.25,
        "verdict": "ai",
        "score": 86
      },
      {
        "t": 3.75,
        "verdict": "ai",
        "score": 88
      },
      {
        "t": 5.25,
        "verdict": "ai",
        "score": 91
      },
      {
        "t": 6.75,
        "verdict": "ai",
        "score": 87
      },
      {
        "t": 8.25,
        "verdict": "ai",
        "score": 90
      },
      {
        "t": 9.75,
        "verdict": "ai",
        "score": 89
      },
      {
        "t": 11.25,
        "verdict": "ai",
        "score": 85
      }
    ]
  }
}
```

A video is read on 8 frames, one at the middle of each of 8 equal slices of the video. Each frame is checked as an image is, both detectors and the watermark check, and the video's reading is the median of the frames'. One scene cut or black frame does not decide it.

`verdict`, `score`, `decided_by` and `lanes` mean what they mean for an image, as [Read the verdict](/docs/guides/check-an-image#read-the-verdict) describes. The provenance lane reads the video file's own C2PA manifest. `video` adds the file's facts and each frame's reading.

| Field            | Description                                                                    |
| ---------------- | ------------------------------------------------------------------------------ |
| `video.seconds`  | The video's length in seconds.                                                 |
| `video.fps`      | Its frame rate.                                                                |
| `video.frames`   | Its frame count.                                                               |
| `video.width`    | Its width in pixels, as it plays.                                              |
| `video.height`   | Its height in pixels, as it plays.                                             |
| `video.audio`    | `copied`, `encoded` or `none`, what an optimization does with its first track. |
| `video.readings` | Each frame's time `t` in seconds, its `verdict` and its `score`.               |

The frames of one video can read far apart, so branch on the video's `verdict`, not on one frame's.

## Optimize a video

**Optimize an uploaded video**

```bash title="cURL"
curl -X POST "https://realpng.com/api/v1/optimize" \
  -H "X-API-Key: $REALPNG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "video_upload_id": "6c3f8e1a-9d4b-4a7c-b2e5-1f8d3c6a9e40",
    "seconds": 12
  }'
```

```javascript title="JavaScript"
const res = await fetch("https://realpng.com/api/v1/optimize", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.REALPNG_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "video_upload_id": "6c3f8e1a-9d4b-4a7c-b2e5-1f8d3c6a9e40",
    "seconds": 12
  }),
});
const data = await res.json();
```

```python title="Python"
import os

import requests

res = requests.post(
    "https://realpng.com/api/v1/optimize",
    headers={"X-API-Key": os.environ["REALPNG_API_KEY"]},
    json={
        "video_upload_id": "6c3f8e1a-9d4b-4a7c-b2e5-1f8d3c6a9e40",
        "seconds": 12,
    },
)
data = res.json()
```

Response `202 Accepted`

```json
{
  "id": "4e7a1c9d-8b2f-4d6e-a3c5-7f0b9e2d6a81",
  "status": "queued",
  "media": "video"
}
```

Then poll the optimization until it finishes.

**Get the video's optimization**

```bash title="cURL"
curl "https://realpng.com/api/v1/optimize/4e7a1c9d-8b2f-4d6e-a3c5-7f0b9e2d6a81" \
  -H "X-API-Key: $REALPNG_API_KEY"
```

```javascript title="JavaScript"
const res = await fetch("https://realpng.com/api/v1/optimize/4e7a1c9d-8b2f-4d6e-a3c5-7f0b9e2d6a81", {
  headers: {
    "X-API-Key": process.env.REALPNG_API_KEY,
  },
});
const data = await res.json();
```

```python title="Python"
import os

import requests

res = requests.get(
    "https://realpng.com/api/v1/optimize/4e7a1c9d-8b2f-4d6e-a3c5-7f0b9e2d6a81",
    headers={"X-API-Key": os.environ["REALPNG_API_KEY"]},
)
data = res.json()
```

Response `200 OK`

```json
{
  "id": "4e7a1c9d-8b2f-4d6e-a3c5-7f0b9e2d6a81",
  "status": "succeeded",
  "media": "video",
  "file_url": "https://realpng.com/api/v1/optimize/4e7a1c9d-8b2f-4d6e-a3c5-7f0b9e2d6a81/file",
  "expires_at": "2026-10-09T09:41:27+00:00",
  "optimization_score": {
    "score": 81,
    "zone": "high"
  },
  "video": {
    "seconds": 12,
    "fps": 30,
    "frames": 360,
    "width": 1280,
    "height": 720,
    "audio": "copied",
    "readings": [
      {
        "t": 0.75,
        "optimization_score": {
          "score": 78,
          "zone": "high"
        }
      },
      {
        "t": 2.25,
        "optimization_score": {
          "score": 80,
          "zone": "high"
        }
      },
      {
        "t": 3.75,
        "optimization_score": {
          "score": 83,
          "zone": "high"
        }
      },
      {
        "t": 5.25,
        "optimization_score": {
          "score": 85,
          "zone": "high"
        }
      },
      {
        "t": 6.75,
        "optimization_score": {
          "score": 79,
          "zone": "high"
        }
      },
      {
        "t": 8.25,
        "optimization_score": {
          "score": 82,
          "zone": "high"
        }
      },
      {
        "t": 9.75,
        "optimization_score": {
          "score": 84,
          "zone": "high"
        }
      },
      {
        "t": 11.25,
        "optimization_score": {
          "score": 77,
          "zone": "high"
        }
      }
    ]
  }
}
```

A succeeded video optimization has `file_url`, the MP4's download link, and no `image_url`. Its `optimization_score` is the median of the frames' readings, with the [zones](/docs/guides/optimize-an-image#read-the-optimization-score) an image's has. The frames are read from the optimized MP4, the file you download. `video.readings` gives each frame's own `optimization_score`.

An optimization of a video takes about as long as the video runs. It applies an image's resample, grain and color passes to every frame. Those passes alone can leave a video's score in the `low` zone.

## Download the MP4

**Download the optimized MP4**

```bash title="cURL"
curl -L -o optimized.mp4 "https://realpng.com/api/v1/optimize/4e7a1c9d-8b2f-4d6e-a3c5-7f0b9e2d6a81/file"
```

```javascript title="JavaScript"
const res = await fetch("https://realpng.com/api/v1/optimize/4e7a1c9d-8b2f-4d6e-a3c5-7f0b9e2d6a81/file");
const image = await res.blob();
```

```python title="Python"
import requests

res = requests.get("https://realpng.com/api/v1/optimize/4e7a1c9d-8b2f-4d6e-a3c5-7f0b9e2d6a81/file")
with open("optimized.mp4", "wb") as file:
    file.write(res.content)
```

```http
HTTP/1.1 200 OK
```

`file_url` needs no API key, and works until `expires_at`, 24 hours after the optimization started. It redirects to a short lived storage link that serves the MP4 as an attachment. That link takes a `Range` header, so a video player can play from it. Follow the redirect, as `curl -L` does.

The MP4 is H.264 with AAC sound, at the source's frame rate.

- Its first audio track is copied as is when it is AAC, and encoded to AAC when it is not.
- Rotation is applied, so a phone video plays upright. HDR comes back as SDR.
- A video over 1920 px on its long edge comes back at 1920 px.
- It is a new file that keeps none of the original's metadata: no container tags, chapters, subtitles, later audio tracks or C2PA manifest.

## From an agent

The MCP tools `audit_video` and `process_video` take the same fields and wait for the job, as [MCP tools](/docs/mcp/tools) describes. `get_processing_result` resumes either one.

## Next steps

- [Credits and limits](/docs/credits-and-limits#video-limits): What a video costs, and its length, size and type limits.

- [Upload a local file](/docs/guides/upload-a-local-file): Send a video from your disk by its upload id.

- [Errors](/docs/errors): Every error code, including a video that runs too long.
