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

TypeFormatsNotes
Image.png, .jpg, .jpeg, .webp, .avif, .gif, .svg, .heic, .heifServed inline at original resolution. SVGs are sanitised on upload.
Video.mp4, .webm, .movA 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, .flacAnnotators 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 to POST /jobs. Treat it as a black-box identifier.
  • type: derived from the file extension (video, image, or audio).
  • 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. Pass media_ref (or media_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 (default 1) and per_page (1–100, default 50) 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} under errors[]) rather than returning a 4xx at POST /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