curl --request POST \
--url https://api.fotolabs.co/v1/images/upload-url \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"filename": "Z6A_0235.NEF",
"projectId": "ca5f09f7-6838-459c-ac0f-7be03c60de24"
}
'import requests
url = "https://api.fotolabs.co/v1/images/upload-url"
payload = {
"filename": "Z6A_0235.NEF",
"projectId": "ca5f09f7-6838-459c-ac0f-7be03c60de24"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({filename: 'Z6A_0235.NEF', projectId: 'ca5f09f7-6838-459c-ac0f-7be03c60de24'})
};
fetch('https://api.fotolabs.co/v1/images/upload-url', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fotolabs.co/v1/images/upload-url",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'filename' => 'Z6A_0235.NEF',
'projectId' => 'ca5f09f7-6838-459c-ac0f-7be03c60de24'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.fotolabs.co/v1/images/upload-url"
payload := strings.NewReader("{\n \"filename\": \"Z6A_0235.NEF\",\n \"projectId\": \"ca5f09f7-6838-459c-ac0f-7be03c60de24\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.fotolabs.co/v1/images/upload-url")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"filename\": \"Z6A_0235.NEF\",\n \"projectId\": \"ca5f09f7-6838-459c-ac0f-7be03c60de24\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fotolabs.co/v1/images/upload-url")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"filename\": \"Z6A_0235.NEF\",\n \"projectId\": \"ca5f09f7-6838-459c-ac0f-7be03c60de24\"\n}"
response = http.request(request)
puts response.read_body{
"imageId": "f3e088d1-33a5-4227-a2f6-c4f75617f847",
"uploadUrl": "https://fotolabs-images-staging.s3.amazonaws.com/...",
"originalImageUrl": "https://d1i1ga8jkrbik1.cloudfront.net/...",
"expiresAt": "2026-05-09T19:00:00.000Z"
}Get a presigned upload URL
Creates a pending image record and returns a presigned S3 PUT URL.
Supported formats: JPEG, PNG, and RAW files — NEF, CR2, CR3, ARW, DNG, ORF, RW2, RAF, PEF, MRW, SRW, RAW.
After receiving the URL, upload the file directly to S3 with a PUT request using the
matching Content-Type header. No auth header is needed on the S3 upload.
The pipeline (RAW vs JPEG) is chosen automatically from the file extension.
Project must be in pending status. Uploads are blocked once processing has started or completed.
curl --request POST \
--url https://api.fotolabs.co/v1/images/upload-url \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"filename": "Z6A_0235.NEF",
"projectId": "ca5f09f7-6838-459c-ac0f-7be03c60de24"
}
'import requests
url = "https://api.fotolabs.co/v1/images/upload-url"
payload = {
"filename": "Z6A_0235.NEF",
"projectId": "ca5f09f7-6838-459c-ac0f-7be03c60de24"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({filename: 'Z6A_0235.NEF', projectId: 'ca5f09f7-6838-459c-ac0f-7be03c60de24'})
};
fetch('https://api.fotolabs.co/v1/images/upload-url', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fotolabs.co/v1/images/upload-url",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'filename' => 'Z6A_0235.NEF',
'projectId' => 'ca5f09f7-6838-459c-ac0f-7be03c60de24'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.fotolabs.co/v1/images/upload-url"
payload := strings.NewReader("{\n \"filename\": \"Z6A_0235.NEF\",\n \"projectId\": \"ca5f09f7-6838-459c-ac0f-7be03c60de24\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.fotolabs.co/v1/images/upload-url")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"filename\": \"Z6A_0235.NEF\",\n \"projectId\": \"ca5f09f7-6838-459c-ac0f-7be03c60de24\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fotolabs.co/v1/images/upload-url")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"filename\": \"Z6A_0235.NEF\",\n \"projectId\": \"ca5f09f7-6838-459c-ac0f-7be03c60de24\"\n}"
response = http.request(request)
puts response.read_body{
"imageId": "f3e088d1-33a5-4227-a2f6-c4f75617f847",
"uploadUrl": "https://fotolabs-images-staging.s3.amazonaws.com/...",
"originalImageUrl": "https://d1i1ga8jkrbik1.cloudfront.net/...",
"expiresAt": "2026-05-09T19:00:00.000Z"
}Authorizations
API key with fl_ prefix, issued from the Fotolabs dashboard
Body
Response
Presigned URL ready. Upload the file within 1 hour.
"f3e088d1-33a5-4227-a2f6-c4f75617f847"
Presigned S3 PUT URL. Upload the file here with the matching Content-Type. Valid for 1 hour.
"https://fotolabs-images-staging.s3.amazonaws.com/..."
CloudFront URL where the original will be accessible after upload.
"https://d1i1ga8jkrbik1.cloudfront.net/..."
When the presigned URL expires.
"2026-05-09T19:00:00.000Z"