Media handling
Upload assets, reference them with dp:// URIs, and how Datapoint serves media to annotators.
Datapoint uses an opaque reference system for media. You upload files once, get a dp:// URI back, and plug that URI into job payloads. Datapoint handles distribution and caching on behalf of annotators.
Supported media types
| Type | Formats | Notes |
|---|---|---|
| Image | .png, .jpg, .jpeg, .webp, .avif, .gif, .svg, .heic, .heif | Served inline at original resolution. SVGs are sanitised on upload. |
| Video | .mp4, .webm, .mov | A first-frame thumbnail is generated the first time the media is used in a job (not for multiple_choice subjects). |
| Audio | .mp3, .wav, .ogg, .m4a, .flac | Annotators see a player control. |
The media type is decided by the file extension, not the declared Content-Type. If you send a Content-Type that contradicts the extension the upload is rejected with 400 (media_type_mismatch); an extension we don't recognise is rejected with 400 (unsupported_media_extension) regardless of Content-Type; an unrecognised Content-Type is ignored.
SVGs are re-serialised when stored, so the bytes you get back aren't byte-identical to what you sent; an SVG that can't be safely parsed is rejected with 400 (invalid_svg).
Upload
Use POST /media with one or more files in a multipart form. The request accepts your API key:
Files in a batch are processed one after another. There is no partial-success rollback — if one file in the batch fails, files already accepted keep their references.
curl -X POST https://api.trydatapoint.com/data-labelling/v1/media \
-H "X-API-Key: $DATAPOINT_KEY" \
-F "files=@video_a.mp4" \
-F "files=@video_b.mp4"The response has one entry per uploaded file:
{
"media": [
{
"filename": "video_a.mp4",
"media_id": "b6a1cd3f-1234-4abc-9def-0123456789ab",
"media_ref": "dp://b6a1cd3f1234/video_a.mp4",
"type": "video",
"size_bytes": 2457623,
"url": "/media/v2/b6a1cd3f-1234-4abc-9def-0123456789ab?cid=...&exp=...&sig=..."
}
]
}The media_ref is always dp:// + 12 lowercase hex characters + / + the original filename you uploaded. Keep it verbatim; the ref is matched as an exact string at dispatch — a mistyped ref passes POST /jobs and fails that datapoint asynchronously, with the reason under errors[] in GET /jobs/{id}.
media_ref: the opaque URI you pass toPOST /jobs. Treat it as a black-box identifier.type: derived from the file extension (video,image, oraudio).size_bytes: file size as received.url: a signed, relative preview path you can fetch to render the file immediately. It expires after about an hour and is scoped to your account — it's a convenience for previews, not the reference you store. Passmedia_ref(ormedia_id) into jobs.
List media
GET /media returns your media library, newest first — direct uploads plus the copies taken from public URLs used in your jobs (URL-sourced items have media_ref: null).
page(default1) andper_page(1–100, default50) paginate the list.
{
"media": [
{
"media_id": "b6a1cd3f-1234-4abc-9def-0123456789ab",
"filename": "video_a.mp4",
"media_ref": "dp://b6a1cd3f1234/video_a.mp4",
"type": "video",
"size_bytes": 2457623,
"moderation_status": "allowed",
"created_at": "2026-07-01T12:00:00Z",
"url": "/media/v2/b6a1cd3f-1234-4abc-9def-0123456789ab?cid=...&exp=...&sig=...",
"thumbnail_url": "/media/v2/b6a1cd3f-1234-4abc-9def-0123456789ab?cid=...&exp=...&sig=...&preview=1"
}
],
"total": 1,
"page": 1,
"per_page": 50
}moderation_status reflects whether an item is available to serve; url and thumbnail_url only resolve for available items. url and thumbnail_url are signed and expiring, same as the upload response. Uploads cannot be deleted through the API.
Using dp:// refs in a job
Every task type accepts dp:// URIs anywhere the media object expects a URL. Pass them inside { url, type } objects. media.candidates is a list of two such objects for a comparison:
{
"datapoints": [
{
"media": {
"candidates": [
{ "url": "dp://b6a1cd3f1234/video_a.mp4", "type": "video" },
{ "url": "dp://c8f3ae9b5678/video_b.mp4", "type": "video" }
]
}
}
]
}dp:// refs are scoped to the account that uploaded them, matched exactly, and can't be deleted. On a dp:// ref the stored file's type is authoritative when the task is served, but a supplied type hint is still what the create-time modality checks read — omit it, or make sure it matches the upload. On a public https:// URL the type is optional when the URL path carries a recognised file extension, and required when the path has no extension; a path ending in an extension we don't recognise is rejected with 400 regardless of type.
Public URLs
Public https:// URLs also work as media references. At dispatch, Datapoint fetches the URL once and stores its own copy — it does not proxy the origin. Practical consequences:
- The source only needs to be reachable at dispatch time. Later changes to the origin do not propagate — the job uses the copy taken at dispatch.
- The source must respond within the fetch time limit (up to a couple of minutes).
- The 20 MiB cap is re-enforced while copying. If the source exceeds it, that datapoint is blocked (the reason appears in
GET /jobs/{id}undererrors[]) rather than returning a4xxatPOST /jobs.
Use public URLs when your assets are already hosted and uploading isn't worth it.
Multiple-choice and text-answer media subjects
A multiple_choice or text_answer datapoint may carry an optional media subject — 0 or 1 pre-uploaded item shown above the question so annotators react to what they see (video is shipped; image and audio are supported). Reference it by a dp:// ref or a bare media_id:
{
"media": {
"subject": [{ "url": "dp://b6a1cd3f1234/clip.mp4", "type": "video" }],
"options": [
{ "id": "opt_1", "text": "Yes" },
{ "id": "opt_2", "text": "No" }
]
}
}Text-only datapoints
Text-only datapoints must send an empty media object explicitly:
{ "media": {} }Omitting media or setting it to null is a 422. An empty media object is only accepted by the rating and text_answer task types. multiple_choice and ranking (and comparison) always carry their options or candidates inside media, so those never use an empty object.
How media is served
When an annotator opens a task, Datapoint serves the asset through a backend proxy URL — storage is never accessed directly. The backend streams the object from its store with:
- Range-request support for video/audio scrubbing
- ETag / conditional GET so browser and CDN caches stay effective
- A default cache lifetime so repeat fetches stay cheap
Videos get a lazily generated first-frame thumbnail the first time they're used; thumbnails are video-only. Storage credentials are never exposed to end users — the proxy path gives you a consistent URL surface instead. (The url and thumbnail_url values returned to you on POST /media and GET /jobs/{id}/results are separately signed and expiring — see above.)
Reuse
Every dp:// URI is durable. You can reference the same upload in multiple jobs without re-uploading, and uploads aren't deletable through the API. There's no per-upload charge; you're only billed for responses collected (see Billing).
Next
- Task types: the media shapes each task type expects
- API Reference: Media: full request/response schema