Direct Upload
Upload a brand image, font, video or audio file in three steps, without sending the bytes through a JSON body
Published
/v1/brand-assets/upload-urlcurl -X POST "https://api.orshot.com/v1/brand-assets/upload-url" \
-H "Authorization: Bearer <ORSHOT_API_KEY>"const res = await fetch("https://api.orshot.com/v1/brand-assets/upload-url", {
method: "POST",
headers: {
Authorization: "Bearer <ORSHOT_API_KEY>",
},
});
const data = await res.json();import requests
response = requests.post(
"https://api.orshot.com/v1/brand-assets/upload-url",
headers={"Authorization": "Bearer <ORSHOT_API_KEY>"},
)
data = response.json()$ch = curl_init("https://api.orshot.com/v1/brand-assets/upload-url");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer <ORSHOT_API_KEY>",
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);require "net/http"
require "json"
uri = URI("https://api.orshot.com/v1/brand-assets/upload-url")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer <ORSHOT_API_KEY>"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)Overview#
Use direct upload when the file is large or when your client cannot build a base64 string. The flow has three steps:
- Ask for an upload URL. It is valid for 5 minutes.
- PUT the raw file bytes to that URL. This request takes no API key; the URL itself is the credential.
- Confirm the upload. The file is checked and stored in your brand library, the same way the
/addendpoints store it.
The same three endpoints work for every asset kind. The image-only paths /images/upload-url, /images/upload and /images/confirm still work for existing clients and behave the same with kind fixed to image.
POST https://api.orshot.com/v1/brand-assets/upload-url
PUT <uploadUrl from step 1>
POST https://api.orshot.com/v1/brand-assets/confirmRequest#
const { data } = await fetch("https://api.orshot.com/v1/brand-assets/upload-url", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
kind: "video",
contentType: "video/mp4",
fileName: "launch-teaser.mp4",
}),
}).then((r) => r.json());
// data.uploadUrl, data.key, data.maxBytes, data.expiresAtcurl -X PUT "<data.uploadUrl>" \
-H "Content-Type: video/mp4" \
--data-binary @launch-teaser.mp4The response is { "data": { "key", "kind", "bytes" } }. No API key is sent on this step.
await fetch("https://api.orshot.com/v1/brand-assets/confirm", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer <ORSHOT_API_KEY>",
},
body: JSON.stringify({
key: data.key,
name: "Launch teaser",
tags: ["launch", "video"],
}),
});{
"data": {
"kind": "video",
"asset": {
"id": 418,
"name": "Launch teaser",
"direct_url": "https://storage.orshot.com/cloud/w-50/renders/brand_assets/x9Kq2mVt7Lp.mp4",
"file_size": 18329044,
"tags": ["launch", "video"],
"workspace_id": 50
},
"url": "https://storage.orshot.com/cloud/w-50/renders/brand_assets/x9Kq2mVt7Lp.mp4"
}
}The asset object has the same fields as the matching /add endpoint for that kind.
Step 1: Upload URL parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
kind | String | Yes | image, font, video or audio. Not needed on the /images/upload-url alias |
contentType | String | Yes | MIME type of the file, for example image/png. Must be one of the kind's supported formats |
fileName | String | No | Used to work out the file extension when contentType is ambiguous |
Response fields:
| Field | Description |
|---|---|
uploadUrl | PUT the file here. Valid for 5 minutes |
directUploadUrl | A second URL for the same upload that goes straight to storage. Use it only if your client cannot PUT to uploadUrl. For SVG, send the headers in directUploadHeaders with it |
key | Pass this to /confirm |
kind, contentType | Echoed back |
maxBytes | The size limit for this kind |
expiresAt | When the upload URL stops working (ISO 8601) |
Step 3: Confirm parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
key | String | Yes | The key from step 1 |
name | String | No | Display name in the library. Falls back to fileName |
fileName | String | No | Original file name |
tags | String[] | No | Tags, stored lowercase |
metadata | Object | No | Arbitrary metadata. For video and audio, pass width, height or duration here if you want them stored; the API does not read them from the file. meta is accepted as an alias for images |
Size limits and formats#
| Kind | Limit | Formats |
|---|---|---|
image | 10MB | PNG, JPEG, WebP, SVG, GIF |
font | 10MB | TTF, OTF, WOFF, WOFF2 |
video | 60MB | MP4, WebM, MOV, AVI, MKV, FLV, WMV, M4V, GIF |
audio | 50MB | MP3, WAV, M4A, AAC, OGG, Opus, FLAC, WebM, AIFF, WMA |
Processing after confirm is the same as for the /add endpoint of each kind: images are optimised and SVGs cleaned, videos may be converted, fonts are parsed for their family name. See Upload Brand Image, Upload Font, Upload Video and Upload Audio for the rules of each kind.
Notes#
- The file type is read from the bytes, not from
contentTypeor the file extension. A file that is not a real image, font, video or audio file is refused at confirm. - Unconfirmed uploads are deleted automatically. Confirm within a few minutes of the PUT.
- A
keycan only be confirmed by the workspace that requested it.
Error responses#
| Step | Status | When |
|---|---|---|
| 1 | 400 | kind missing or unknown, or contentType missing or not supported for that kind |
| 2 | 400 | Empty body. Send the raw bytes, for example curl -X PUT --data-binary @file |
| 2 | 403 | The upload URL was changed or is not valid |
| 2 | 410 | The upload URL has expired. Request a new one |
| 2 | 413 | The file is larger than the kind's limit |
| 2 | 429 | More than 60 PUTs in a minute for one workspace |
| 3 | 400 | key missing, the file is over the limit, or the bytes are not a valid file of that kind. Image problems carry the same code values as Upload Brand Image |
| 3 | 403 | The key belongs to another workspace or was not issued by this API |
| 3 | 404 | Nothing was uploaded for this key yet, or the unconfirmed file was already cleaned up |
Query Parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
embedId | String | No | Embed instance ID. Pair with embedUserId to scope the asset to one embed user |
embedUserId | String | No | The same userId your app passes to the embed URL. Requires embedId |
Pass them on the /confirm call, which is where the asset is created. See Per-User Brand Assets.
Rate Limits#
- Steps 1 and 3: 30 requests per minute per endpoint
- Step 2: 60 PUTs per minute per workspace
Ready to automate?
Start rendering images, PDFs and videos from your templates in under 2 minutes. Free plan, no credit card.
Get your API key- Image, PDF and video generation via API
- Visual editor with AI and smart layouts
- Zapier, Make, MCP and 40+ integrations
- White-label embed for your own app
- 100 free credits a month, no credit card required