Media

Attach media

Turn an image / video into a popkit media URL. Media used for publishing must live in popkit storage (external links expire), so get a mediaUrl here first and put it into media[].dataUrl on create / update. mode=upload returns a direct-upload URL valid for five minutes; PUT the bytes yourself with the returned Content-Type (bytes never touch popkit servers: images 8 MB, videos 500 MB). mode=transload lets popkit fetch and copy a public link — images and covers only, 8 MB max. It only produces a URL and changes nothing else.

POST/api/v1/media
posts:writewrite tierIdempotency-Key optional

Authorization & guardrails

  • Send Authorization: Bearer <credential>; credentials are issued under Settings → Integrations → Long-lived credentials, see Authentication.
  • The credential needs scope posts:write; the issuer's workspace role must also allow the action.
  • Rate limit: write tier, 10/min per credential; over the cap returns 429 with Retry-After.
  • Idempotency: honoured when Idempotency-Key is present; optional otherwise.

Request body (application/json)

modeenum: upload · transloadrequired

upload = get a direct-upload URL and PUT yourself; transload = popkit fetches your link.

kindenum: image · cover · videorequired

image body picture · cover · video.

contentTypestringoptional

Required for upload: image/jpeg | png | webp or video/mp4 | webm | quicktime.

bytesintegeroptional

Required for upload: file size in bytes; over the limit is refused.

urlstring (http/https)optional

Required for transload: the link to copy (images / covers only).

clientRequestIdstringoptional

Idempotency key (8–128 chars of [A-Za-z0-9_.:-]); prefer the Idempotency-Key header, which wins over the body.

Rules

  • The upload URL lives 5 minutes; upload right away and re-request if it expires.
  • Videos always use upload; transload accepts images and covers only.

Responses

Success is HTTP 201 with a { data, meta } envelope; examples per status are on the right (at the end of the page on narrow screens). Every error is { error: { code, message, params }, meta } — branch on the code, not the message; see Errors.

StatusPossible codes
400invalid_input
401unauthenticated
402plan_limit
403forbidden / workspace_not_found
404not_found
409no_account / needs_reauth / replay
429rate_limited
500internal
502platform_error
503external_writes_off

Examples

Request
curl -X POST "https://popkit.loxily.com/api/v1/media" \
  -H "Authorization: Bearer popkit_it_••••••••" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: acme-2026-09-21-001" \
  -d '{
  "mode": "upload",
  "kind": "video",
  "contentType": "video/mp4",
  "bytes": 48213000
}'
Response
upload
{
  "data": {
    "mode": "upload",
    "kind": "video",
    "mediaUrl": "https://storage.googleapis.com/socialhub-media/ws_01J7…/media/01J8ZN….mp4",
    "uploadUrl": "https://storage.googleapis.com/socialhub-media/…?X-Goog-Signature=…",
    "contentType": "video/mp4",
    "expiresInSec": 300,
    "next": "PUT the file bytes to uploadUrl with Content-Type: video/mp4 within 300s, then pass mediaUrl in media[].dataUrl."
  },
  "meta": {
    "requestId": "6a1d0c9e-2b7f-4e3a-8c5d-1f0e9b8a7c73"
  }
}