Skip to main content

File uploads

Some write endpoints accept images — an event's featured image and gallery, for example. You don't post file bytes to the API directly. Instead you request a short-lived direct upload, send the bytes straight to storage, and then reference the uploaded file by its signed id from a write request.

It's a three-step flow:

  1. POST /uploads to register the file and get a presigned upload URL + a signed id.
  2. PUT the raw bytes to that URL.
  3. Pass the signed id to a write field (e.g. featuredImageId on POST /events).

OAuth tokens need the upload_files scope to call POST /uploads.

1. Request a direct upload

POST /2025_06/uploads
{
"filename": "poster.png",
"byteSize": 482113,
"checksum": "Base64MD5OfTheFile==",
"contentType": "image/png"
}

checksum is the base64-encoded MD5 digest of the file's contents — storage uses it to verify the upload arrived intact. The response is 201 Created:

{
"object": "direct_upload",
"id": "signed-id-string",
"uploadUrl": "https://…",
"uploadHeaders": { "Content-Type": "image/png", "Content-MD5": "Base64MD5OfTheFile==" },
"maxBytes": 10485760,
"expiresAt": "2026-06-19T12:05:00Z"
}
  • id — the signed id you reference from a write endpoint.
  • uploadUrl — where you PUT the bytes. Short-lived; it stops working after expiresAt.
  • uploadHeaders — send these verbatim on the PUT.
  • maxBytes — the maximum accepted file size.

2. Upload the bytes

PUT the raw file to uploadUrl, forwarding uploadHeaders. This request goes directly to object storage, not to api.viewcy.com:

curl -X PUT "$uploadUrl" \
-H "Content-Type: image/png" \
-H "Content-MD5: Base64MD5OfTheFile==" \
--data-binary @poster.png

3. Reference the upload

Pass the signed id to a write field that takes a blob reference:

POST /2025_06/events
{
"name": "Tuesday Night Jazz",
"category": "Concerts",
"featuredImageId": "signed-id-string"
}

Notes and limits

  • Rate limit: 30 uploads per 10 minutes (a separate bucket from reads and writes).
  • Idempotency is not supported. POST /uploads ignores the Idempotency-Key header — a replayed response could hand back an already-expired URL. Just request a fresh upload; it's cheap.
  • Reference it promptly. An uploaded file that isn't attached to a resource is purged after 12 hours.
  • Not a queryable resource. There is no GET /uploads/{id}; the signed id is the only handle you get.