403 Forbidden on Transcode
Where a 403 in the upload and transcode flow comes from, and how to fix it
The FFmpeg Micro API does not answer a transcode request with a 403. If you see one, it came from Google Cloud Storage, on the signed link you upload to. The quickest way to tell is the response body: storage answers in XML, and the API answers in JSON.
Upload Content Type
A mismatched content type on the upload is what causes a 403 in this flow. When you ask for an upload link, you name the file's contentType. The link is signed for that exact type. If the Content-Type header on your upload is anything else, storage refuses it:
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>Access denied.</Message>
</Error>
Send the same value in both places:
# 1. Ask for the upload link, naming the content type
curl -X POST "https://api.ffmpeg-micro.com/v1/upload/presigned-url" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "filename": "video.mp4", "contentType": "video/mp4", "fileSize": 12345678 }'
# 2. Upload with the SAME content type
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: video/mp4" \
--data-binary @video.mp4
Tools that set the header for you are the usual culprit. An HTTP client that sends application/octet-stream or multipart/form-data by default will be refused. Set the header yourself, and send the file as the raw request body.
Expired Links
An expired link is a 400, not a 403. Storage answers with the code ExpiredToken.
POST /v1/upload/presigned-url.GET /v1/transcodes/:id/download. You can do this as often as you like.Errors Mistaken for a 403
These come from the API, in JSON, and each has its own status.
400 "Placeholder bucket name": the input address uses a bucket name copied from an example, such as gs://your-bucket/video.mp4.
400 "Invalid input": the input address names a bucket that is not your upload bucket.
For both, use the fileUrl that POST /v1/upload/confirm returned, exactly as it came back. Do not build the address yourself.
401: the API key is missing, mistyped or revoked. See Getting Started with Your API Key.
402 Payment Required: the account has nothing left to process with. See How to Check Your Usage and Quota.
Upload and Transcode Flow
The whole sequence, with the file's address taken from the confirm response:
# The file's real size in bytes. The API checks it.
FILE_SIZE=$(wc -c < video.mp4 | tr -d ' ')
# 1. Get an upload link
UPLOAD_RESPONSE=$(curl -s -X POST "https://api.ffmpeg-micro.com/v1/upload/presigned-url" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d "{ \"filename\": \"video.mp4\", \"contentType\": \"video/mp4\", \"fileSize\": $FILE_SIZE }")
UPLOAD_URL=$(echo $UPLOAD_RESPONSE | jq -r '.result.uploadUrl')
FILENAME=$(echo $UPLOAD_RESPONSE | jq -r '.result.filename')
# 2. Upload the file, with the same content type
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: video/mp4" \
--data-binary @video.mp4
# 3. Confirm the upload and keep the address it returns
FILE_URL=$(curl -s -X POST "https://api.ffmpeg-micro.com/v1/upload/confirm" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d "{ \"filename\": \"$FILENAME\", \"fileSize\": $FILE_SIZE }" | jq -r '.result.fileUrl')
# 4. Create the transcode job with that address
curl -X POST "https://api.ffmpeg-micro.com/v1/transcodes" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d "{ \"inputs\": [{ \"url\": \"$FILE_URL\" }], \"outputFormat\": \"mp4\", \"preset\": { \"quality\": \"medium\" } }"
Support
If a 403 does not fit the case above, write to javid@ffmpeg-micro.com with:
Do not send your API key.
Related Articles
Learn how to set up authentication and make your first API request
Where to see your usage limits and what happens when you hit quota
What the file size mismatch error means and how to fix it
Can't find what you're looking for?