SquidX logo

Media

Images and videos for posts. Bytes wait in SquidX storage and reach X at publish time, because X media ids expire after about a day and a scheduled post can wait for weeks. A segment carries up to four images, or one GIF alone, or one video alone.

Upload an image

POSTsquidx.io/api/v1/media
Requiresposts:write

Multipart upload of one JPEG, PNG, WebP or GIF, 4MB max. Put the returned id in a segment's media list when creating or editing a post. Up to four images per segment, or one GIF alone. Videos use the two-step upload below.

Upload an image parameters
NameInTypeDescription
filerequiredform-datafileThe image. image/jpeg, image/png, image/webp or image/gif.
Request
curl -X POST https://squidx.io/api/v1/media \
  -H "Authorization: Bearer sqx_your_key" \
  -F "file=@./launch.png"
Response · 201
{
  "data": {
    "id": "3c1c9e5a-0d2b-4f6e-8a7c-1b2d3e4f5a6b/7d2e1f0a-9b8c-4d7e-a6f5-4e3d2c1b0a99.png",
    "path": "3c1c9e5a-0d2b-4f6e-8a7c-1b2d3e4f5a6b/7d2e1f0a-9b8c-4d7e-a6f5-4e3d2c1b0a99.png",
    "type": "photo"
  }
}
  • Attaching media needs a paid workspace, like everything that can end up on X.
  • Then: POST /posts with segments: [{ text: "...", media: [{ id: "<id>" }] }].

Upload an image from a URL or base64

POSTsquidx.io/api/v1/media
Requiresposts:write

The same upload with a JSON body, for agents and scripts that hold a link or the bytes rather than a file. SquidX downloads a public https URL itself, or decodes base64 data. Same types and 4MB cap.

Upload an image from a URL or base64 parameters
NameInTypeDescription
urlbodystringA public https URL of the image. Private and local addresses are refused.
databodystringThe image as base64 (a data: prefix is accepted). Give mimeType with it. Send url or data, not both.
mimeTypebodystringWith data: image/jpeg, image/png, image/webp or image/gif.
Request
curl -X POST https://squidx.io/api/v1/media \
  -H "Authorization: Bearer sqx_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/launch.png" }'
Response · 201
{
  "data": {
    "id": "3c1c9e5a-0d2b-4f6e-8a7c-1b2d3e4f5a6b/7d2e1f0a-9b8c-4d7e-a6f5-4e3d2c1b0a99.png",
    "path": "3c1c9e5a-0d2b-4f6e-8a7c-1b2d3e4f5a6b/7d2e1f0a-9b8c-4d7e-a6f5-4e3d2c1b0a99.png",
    "type": "photo"
  }
}
  • The download follows up to three redirects and stops after 10 seconds. Videos are refused here: use the two-step upload below.

Start a video upload

POSTsquidx.io/api/v1/media/uploads
Requiresposts:write

Step 1 of 2 for a video. SquidX answers a signed URL you PUT the file to, straight into storage: a video is too big for a normal request. MP4 or MOV, 100MB and 140 seconds max. The URL is good for two hours.

Start a video upload parameters
NameInTypeDescription
mimeTyperequiredbodystringvideo/mp4 or video/quicktime.
sizerequiredbodyintegerThe file size in bytes. SquidX measures the real object at step 2.
Request
curl -X POST https://squidx.io/api/v1/media/uploads \
  -H "Authorization: Bearer sqx_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "mimeType": "video/mp4", "size": 18350212 }'
Response · 201
{
  "data": {
    "path": "3c1c9e5a-0d2b-4f6e-8a7c-1b2d3e4f5a6b/2b7c1d9e-4f5a-4b6c-8d7e-9f0a1b2c3d4e.mp4",
    "signedUrl": "https://<project>.supabase.co/storage/v1/object/upload/sign/post-media/3c1c9e5a-.../2b7c1d9e-....mp4?token=eyJ...",
    "token": "eyJ...",
    "maxBytes": 104857600,
    "expiresInSeconds": 7200
  }
}
  • Then PUT the bytes: curl -X PUT "<signedUrl>" -H "content-type: video/mp4" -H "x-upsert: false" --data-binary @clip.mp4
  • Recommended encoding: MP4, H.264 video, AAC audio. X checks the codec at publish time; a refusal shows on the post as lastError media_upload.

Finish a video upload

POSTsquidx.io/api/v1/media/uploads/complete
Requiresposts:write

Step 2 of 2. SquidX reads the stored object back, checks its size and type, and answers the media id. Put it in a segment's media list, alone.

Finish a video upload parameters
NameInTypeDescription
pathrequiredbodystringThe path from step 1.
Request
curl -X POST https://squidx.io/api/v1/media/uploads/complete \
  -H "Authorization: Bearer sqx_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "path": "3c1c9e5a-0d2b-4f6e-8a7c-1b2d3e4f5a6b/2b7c1d9e-4f5a-4b6c-8d7e-9f0a1b2c3d4e.mp4" }'
Response · 201
{
  "data": {
    "id": "3c1c9e5a-0d2b-4f6e-8a7c-1b2d3e4f5a6b/2b7c1d9e-4f5a-4b6c-8d7e-9f0a1b2c3d4e.mp4",
    "path": "3c1c9e5a-0d2b-4f6e-8a7c-1b2d3e4f5a6b/2b7c1d9e-4f5a-4b6c-8d7e-9f0a1b2c3d4e.mp4",
    "type": "video"
  }
}
  • A missing object answers 404 missing (the PUT did not finish). A file that is not a video we accept answers 400 invalid and is removed.
  • Then: POST /posts with segments: [{ text: "...", media: [{ id: "<id>" }] }]. A video travels alone: no image next to it.