Upload media
Create a presigned upload URL for media used by the Social Service publish API
This guide is for developers who need to publish local media files. The upload endpoint creates a temporary presigned PUT URL. It does not upload file bytes by itself.
After the PUT upload succeeds, use the returned temporaryUrl.url or publishAsset.source as the media source in /api/social/v1/post.
Endpoint
POST /api/social/v1/media/uploadProduction URL:
https://sogrowly.com/api/social/v1/media/uploadAuthentication
Requests require a Bearer Social API key. The API key must include the social:publish permission:
Authorization: Bearer sma_live_xxx
Content-Type: application/jsonRequest Body
{
"mimeType": "image/png",
"fileName": "post-image.png"
}Fields:
mimeType Required. Supported values: image/png, image/jpeg, image/webp, video/mp4, application/pdf.
fileName Optional. Used to build a safe object key. If omitted, the service uses a generated name.The uploaded object is stored under the tmp_file_upload/ prefix. The service generates the final object key and ensures the file extension matches the MIME type.
Successful Response
{
"status": "success",
"statusCode": 200,
"data": {
"objectKey": "tmp_file_upload/uuid-post-image.png",
"mimeType": "image/png",
"assetType": "image",
"upload": {
"method": "PUT",
"url": "https://example-presigned-upload-url",
"headers": {
"Content-Type": "image/png"
},
"expiresInSeconds": 300,
"expiresAt": "2026-06-10T10:05:00.000Z"
},
"temporaryUrl": {
"url": "https://files.example.com/tmp_file_upload/uuid-post-image.png",
"expiresInSeconds": 86400,
"expiresAt": "2026-06-11T10:00:00.000Z",
"availableAfterUpload": true
},
"publishAsset": {
"type": "image",
"source": "https://files.example.com/tmp_file_upload/uuid-post-image.png",
"sourceType": "url",
"mimeType": "image/png"
}
},
"message": "Media upload URL created. Upload the file bytes with HTTP PUT before using the temporary URL.",
"meta": {
"version": "v1"
}
}Important: temporaryUrl.url is not usable until the file bytes are uploaded successfully to upload.url.
Upload the File Bytes
Use the returned upload.method, upload.url, and upload.headers to upload the local file bytes:
const uploadResponse = await fetch(result.data.upload.url, {
method: result.data.upload.method,
headers: result.data.upload.headers,
body: fileBytes,
});
if (!uploadResponse.ok) {
throw new Error(`Upload failed: ${uploadResponse.status}`);
}Browser example with a File object:
const uploadResponse = await fetch(result.data.upload.url, {
method: 'PUT',
headers: result.data.upload.headers,
body: file,
});cURL example:
curl -X PUT "https://example-presigned-upload-url" \
-H "Content-Type: image/png" \
--data-binary "@post-image.png"Publish With Uploaded Media
After the upload succeeds, pass the returned publishAsset to /api/social/v1/post:
{
"workspaceId": "sws_123",
"draft": {
"text": "New product image.",
"assets": [
{
"type": "image",
"source": "https://files.example.com/tmp_file_upload/uuid-post-image.png",
"sourceType": "url",
"mimeType": "image/png"
}
]
},
"targets": [
{
"channelId": "sch_123"
}
]
}The publish target uses channelId. Query publishable channels from /api/social/v1/account before publishing.
Common Errors
Unsupported MIME type:
{
"status": "failed",
"statusCode": 400,
"data": {},
"message": "Request body is invalid.",
"error": {
"code": "invalid_payload",
"message": "Request body is invalid.",
"details": {
"issues": [
{
"code": "invalid_value",
"path": "mimeType",
"message": "Invalid option: expected one of ..."
}
]
}
},
"meta": {
"version": "v1"
}
}API key missing the publish permission:
{
"status": "failed",
"statusCode": 403,
"data": {},
"message": "API key requires \"social:publish\" permission.",
"error": {
"code": "workspace_permission_denied",
"message": "API key requires \"social:publish\" permission."
},
"meta": {
"version": "v1"
}
}Status Codes and Rate Limits
200 -> Upload URL created
400 -> Invalid request body or unsupported MIME type
401 -> API key is missing, malformed, invalid, or expired
403 -> API key permission is insufficient
429 -> Rate limited by userId
500 -> Configuration error or unknown errorCurrent rate limit:
Each resolved userId can make up to 15 requests per minute.
The account, media upload, and post endpoints share this rate limit.