How It Works
After initiating an upload, you receive a presigned upload_url and a set of required_headers. Send a PUT request with your file binary to that URL, including all required headers exactly as provided.
If any header is missing or doesn’t match, the upload will be rejected with HTTP 403 Forbidden. The presigned URL is cryptographically bound to the exact headers returned.
Request
PUT {upload_url from Initiate Upload response}
Include every key-value pair from required_headers as HTTP headers on the PUT request.
Code Examples
curl -X PUT \
-H "Content-Type: audio/mp4" \
-H "x-amz-meta-file-id: 550e8400-e29b-41d4-a716-446655440000" \
-H "x-amz-meta-expires-at: 2026-09-01T10:00:00Z" \
-H "x-amz-meta-audio-duration-seconds: 1008.32" \
-H "x-amz-tagging: uploader=customer&ttl_days=30" \
--data-binary @audio.m4a \
"https://s3.us-east-1.amazonaws.com/bucket/uploads/550e8400.m4a?X-Amz-Algorithm=..."
Response
A successful upload returns HTTP 200 OK with an empty body.
| HTTP Status | Meaning |
|---|
200 | File uploaded successfully |
403 | Missing or mismatched headers — ensure all required_headers are included exactly |
400 | File too large or malformed request |
Full Example
Here’s the complete flow — initiate, upload, then check status:
import requests
import time
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://storage-service.core.eachlabs.run"
# Step 1: Initiate
init_response = requests.post(
f"{BASE_URL}/api/v1/uploads",
headers={
"Content-Type": "application/json",
"X-API-Key": API_KEY
},
json={
"owner": "org-your-org-id",
"uploader": "customer",
"file_type": "audio",
"content_type": "audio/mp4"
}
)
data = init_response.json()
file_id = data["id"]
# Step 2: Upload
with open("audio.m4a", "rb") as f:
requests.put(data["upload_url"], headers=data["required_headers"], data=f)
# Step 3: Poll until complete
while True:
status_response = requests.get(
f"{BASE_URL}/api/v1/uploads/{file_id}",
headers={"X-API-Key": API_KEY}
)
status = status_response.json()
if status["status"] in ("COMPLETED", "ERROR"):
break
print(f"Status: {status['status']}...")
time.sleep(3)
print(f"Done! Status: {status['status']}, Size: {status.get('size_bytes')} bytes")
If you provided a callback_url when initiating the upload, you’ll receive a webhook notification when processing completes — no polling needed.