Skip to content

Media and image uploads

Uploads do not pass through the API. You ask for a presigned URL, you PUT the bytes at object storage, and you tell us it finished. Three requests, and the file never touches our bandwidth or our memory.

Last updated

Endpoints

MethodPathWhat it doesCredential
POST/v1/blog/mediaRecord a file and get an upload URLSession or agent
POST/v1/blog/media/{id}/completeConfirm the upload finishedSession or agent
GET/v1/blog/mediaList mediaKey or session
DELETE/v1/blog/media/{id}Delete mediaSession or agent

The three requests

  1. Ask for a URL

    Send the filename, the media type, the size and a SHA-256 of the bytes. You get back a media row with uploadStatus: "pending" and a presigned URL that expires shortly.

    Examplebash
    curl -X POST https://api.cmskite.com/v1/blog/media \
      -H "authorization: Bearer $ACCESS_TOKEN" \
      -H "x-tenant-id: $WORKSPACE_ID" \
      -H "x-project-id: $PROJECT_ID" \
      -H 'content-type: application/json' \
      -d '{
        "filename": "rotterdam.jpg",
        "mimeType": "image/jpeg",
        "sizeBytes": 481223,
        "checksumSha256": "9f86d0818…"
      }'
  2. PUT the bytes

    Straight at object storage, from wherever the file is — including the visitor’s browser. No credential of yours is involved; the presigned URL is the authorisation, and it is narrow and short-lived.

    Examplebash
    curl -X PUT "$UPLOAD_URL" \
      -H 'content-type: image/jpeg' \
      --data-binary @rotterdam.jpg
  3. Confirm it

    The media row flips to ready and becomes visible to a project key. Until then it is not served: a half-finished upload is not content.

    Examplebash
    curl -X POST https://api.cmskite.com/v1/blog/media/$MEDIA_ID/complete \
      -H "authorization: Bearer $ACCESS_TOKEN" \
      -H "x-tenant-id: $WORKSPACE_ID" \
      -H "x-project-id: $PROJECT_ID"

The same file twice is the same file

Media is content-addressed by its checksum within a project. Re-uploading the same logo for the fortieth time returns the row you already have instead of making a fortieth copy — which is the most common thing a media library is asked to do, and makes uploading idempotent for free.

What is accepted

  • Images and documents. A text/html upload is refused: a file served from your domain that can run script is not a media library, it is an XSS vector.
  • A malformed checksum or a negative size is a 400 before any URL is issued.
  • Storage is metered per plan. blog.media.storage_bytes is the capability; your dashboard shows what you have used.
  • An upload that is never confirmed is reaped. A presigned URL that nobody used costs you nothing.