Skip to content

Upload API

Upload images and videos to Reimage storage.

Endpoint

http
POST /upload/

Authentication

Required. Include your API key in the Authorization header.

http
Authorization: Bearer YOUR_API_KEY

Request Format

Content-Type: multipart/form-data

Parameters

ParameterTypeRequiredDescription
fileFileYesThe image or video file to upload
tagsStringNoComma-separated list of tags (e.g., "product,hero")
folderStringNoFolder/directory to organize the file
objectStringNoCustom object ID (defaults to random 12-char hex)

Supported File Types

Images:

  • JPEG (.jpg, .jpeg)
  • PNG (.png)
  • GIF (.gif) - including animated
  • WebP (.webp)
  • AVIF (.avif)

Videos:

  • MP4 (.mp4)
  • MOV (.mov)
  • AVI (.avi)
  • And other common formats

Response

Image Upload Response

json
{
  "object_url": "https://files.reimage.dev/abc123/xyz789/",
  "original": "https://files.reimage.dev/abc123/xyz789/original",
  "optimised": "https://files.reimage.dev/abc123/xyz789/original.webp",
  "object_id": "xyz789",
  "tags": ["product", "hero"],
  "size": 245.52,
  "type": "image/jpeg",
  "folder": "products",
  "original_file_name": "product-photo.jpg",
  "height": 1920,
  "width": 1080
}

Video Upload Response

json
{
  "object_url": "https://files.reimage.dev/abc123/vid-xyz789/",
  "original": "https://files.reimage.dev/abc123/vid-xyz789/original",
  "object_id": "vid-xyz789",
  "tags": ["demo", "tutorial"],
  "size": 5242.88,
  "type": "video/mp4",
  "folder": "videos",
  "original_file_name": "demo-video.mp4",
  "video_url": "https://files.reimage.dev/abc123/vid-xyz789/original.m3u8"
}

Response Fields

FieldTypeDescription
object_urlStringBase URL for accessing the file
originalStringURL to the original file
optimisedStringURL to optimized WebP version (images only)
object_idStringUnique identifier (videos prefixed with "vid-")
tagsArrayTags associated with the file
sizeNumberFile size in kilobytes
typeStringMIME type of the uploaded file
folderStringFolder location (null if not specified)
original_file_nameStringOriginal filename
heightNumberImage height in pixels (images only)
widthNumberImage width in pixels (images only)
video_urlStringHLS streaming URL (videos only)

Code Examples

javascript
async function uploadImage(file, tags = []) {
  const formData = new FormData();
  formData.append('file', file);
  formData.append('tags', tags.join(','));
  formData.append('folder', 'products');

  const response = await fetch('https://api.reimage.dev/upload/', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.REIMAGE_API_KEY}`
    },
    body: formData
  });

  if (!response.ok) {
    throw new Error(`Upload failed: ${response.status}`);
  }

  return await response.json();
}

// Usage
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];

const result = await uploadImage(file, ['product', 'hero']);
console.log(`Uploaded: ${result.object_id}`);
console.log(`URL: ${result.object_url}`);
javascript
import { useState } from 'react';

function ImageUploader() {
  const [uploading, setUploading] = useState(false);
  const [result, setResult] = useState(null);
  const [error, setError] = useState(null);

  const handleUpload = async (e) => {
    const file = e.target.files[0];
    if (!file) return;

    setUploading(true);
    setError(null);

    const formData = new FormData();
    formData.append('file', file);
    formData.append('tags', 'user-upload,gallery');

    try {
      const response = await fetch('https://api.reimage.dev/upload/', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.REACT_APP_REIMAGE_API_KEY}`
        },
        body: formData
      });

      if (!response.ok) {
        const error = await response.json();
        throw new Error(error.error || 'Upload failed');
      }

      const data = await response.json();
      setResult(data);
    } catch (err) {
      setError(err.message);
    } finally {
      setUploading(false);
    }
  };

  return (
    <div>
      <input
        type="file"
        accept="image/*,video/*"
        onChange={handleUpload}
        disabled={uploading}
      />

      {uploading && <p>Uploading...</p>}
      {error && <p style={{ color: 'red' }}>{error}</p>}

      {result && (
        <div>
          <p>Upload successful!</p>
          <p>Object ID: {result.object_id}</p>
          {result.height && (
            <>
              <p>Dimensions: {result.width} x {result.height}</p>
              <img
                src={`${result.object_url}w-400.webp`}
                alt="Uploaded"
                width="400"
              />
            </>
          )}
        </div>
      )}
    </div>
  );
}
python
import requests
import os

def upload_image(file_path, tags=None, folder=None):
    """Upload an image to Reimage API"""
    url = 'https://api.reimage.dev/upload/'
    headers = {
        'Authorization': f'Bearer {os.environ.get("REIMAGE_API_KEY")}'
    }

    with open(file_path, 'rb') as f:
        files = {'file': (os.path.basename(file_path), f)}
        data = {}

        if tags:
            data['tags'] = ','.join(tags)
        if folder:
            data['folder'] = folder

        response = requests.post(url, files=files, data=data, headers=headers)
        response.raise_for_status()

        return response.json()

# Usage
result = upload_image(
    'product-image.jpg',
    tags=['product', 'featured'],
    folder='products'
)

print(f"Uploaded: {result['object_id']}")
print(f"Size: {result['size']} KB")
print(f"Dimensions: {result['width']} x {result['height']}")
print(f"Optimized URL: {result['optimised']}")
php
<?php
function uploadImage($filePath, $tags = [], $folder = null) {
    $apiKey = getenv('REIMAGE_API_KEY');
    $url = 'https://api.reimage.dev/upload/';

    $cfile = new CURLFile($filePath, mime_content_type($filePath), basename($filePath));

    $postData = ['file' => $cfile];

    if (!empty($tags)) {
        $postData['tags'] = implode(',', $tags);
    }

    if ($folder) {
        $postData['folder'] = $folder;
    }

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $apiKey
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpCode !== 200) {
        throw new Exception("Upload failed with status: $httpCode");
    }

    return json_decode($response, true);
}

// Usage
$result = uploadImage(
    'product-image.jpg',
    ['product', 'featured'],
    'products'
);

echo "Uploaded: " . $result['object_id'] . "\n";
echo "URL: " . $result['object_url'] . "\n";
?>
go
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "mime/multipart"
    "net/http"
    "os"
    "path/filepath"
    "strings"
)

type UploadResponse struct {
    ObjectURL        string   `json:"object_url"`
    Original         string   `json:"original"`
    Optimised        string   `json:"optimised"`
    ObjectID         string   `json:"object_id"`
    Tags             []string `json:"tags"`
    Size             float64  `json:"size"`
    Type             string   `json:"type"`
    Folder           string   `json:"folder"`
    OriginalFileName string   `json:"original_file_name"`
    Height           int      `json:"height,omitempty"`
    Width            int      `json:"width,omitempty"`
}

func uploadImage(apiKey, filePath string, tags []string, folder string) (*UploadResponse, error) {
    file, err := os.Open(filePath)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)

    part, err := writer.CreateFormFile("file", filepath.Base(filePath))
    if err != nil {
        return nil, err
    }
    io.Copy(part, file)

    if len(tags) > 0 {
        writer.WriteField("tags", strings.Join(tags, ","))
    }

    if folder != "" {
        writer.WriteField("folder", folder)
    }

    writer.Close()

    req, err := http.NewRequest("POST", "https://api.reimage.dev/upload/", body)
    if err != nil {
        return nil, err
    }

    req.Header.Set("Authorization", "Bearer "+apiKey)
    req.Header.Set("Content-Type", writer.FormDataContentType())

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    if resp.StatusCode != 200 {
        return nil, fmt.Errorf("upload failed with status: %d", resp.StatusCode)
    }

    var result UploadResponse
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return nil, err
    }

    return &result, nil
}

func main() {
    apiKey := os.Getenv("REIMAGE_API_KEY")
    result, err := uploadImage(
        apiKey,
        "product-image.jpg",
        []string{"product", "featured"},
        "products",
    )

    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }

    fmt.Printf("Uploaded: %s\n", result.ObjectID)
    fmt.Printf("Size: %.2f KB\n", result.Size)
    fmt.Printf("Dimensions: %d x %d\n", result.Width, result.Height)
}
rust
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION};
use reqwest::multipart;
use serde::{Deserialize, Serialize};
use std::fs::File;

#[derive(Debug, Deserialize, Serialize)]
struct UploadResponse {
    object_url: String,
    original: String,
    optimised: Option<String>,
    object_id: String,
    tags: Vec<String>,
    size: f64,
    #[serde(rename = "type")]
    file_type: String,
    folder: Option<String>,
    original_file_name: String,
    height: Option<u32>,
    width: Option<u32>,
}

async fn upload_image(
    api_key: &str,
    file_path: &str,
    tags: Vec<&str>,
    folder: Option<&str>,
) -> Result<UploadResponse, Box<dyn std::error::Error>> {
    let file = File::open(file_path)?;
    let file_name = std::path::Path::new(file_path)
        .file_name()
        .unwrap()
        .to_str()
        .unwrap();

    let file_part = multipart::Part::reader(file)
        .file_name(file_name.to_string())
        .mime_str("image/jpeg")?;

    let mut form = multipart::Form::new().part("file", file_part);

    if !tags.is_empty() {
        form = form.text("tags", tags.join(","));
    }

    if let Some(f) = folder {
        form = form.text("folder", f.to_string());
    }

    let mut headers = HeaderMap::new();
    headers.insert(
        AUTHORIZATION,
        HeaderValue::from_str(&format!("Bearer {}", api_key))?,
    );

    let client = reqwest::Client::new();
    let response = client
        .post("https://api.reimage.dev/upload/")
        .headers(headers)
        .multipart(form)
        .send()
        .await?;

    if !response.status().is_success() {
        return Err(format!("Upload failed: {}", response.status()).into());
    }

    let result = response.json::<UploadResponse>().await?;
    Ok(result)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let api_key = std::env::var("REIMAGE_API_KEY")?;

    let result = upload_image(
        &api_key,
        "product-image.jpg",
        vec!["product", "featured"],
        Some("products"),
    )
    .await?;

    println!("Uploaded: {}", result.object_id);
    println!("Size: {:.2} KB", result.size);
    if let (Some(w), Some(h)) = (result.width, result.height) {
        println!("Dimensions: {} x {}", w, h);
    }

    Ok(())
}

Storage Limits

Uploads are subject to storage limits based on your subscription tier:

  • Free Tier: 100 MB
  • Pro Tier: 10 GB
  • Enterprise: Custom limits

When you reach 95% of your limit, you'll receive an email notification.

Error Response (Limit Exceeded)

json
{
  "error": "Storage limit exceeded"
}

Status Code: 403 Forbidden

Video Processing

Videos are processed asynchronously using Mux:

  1. Upload returns immediately with a video_url
  2. Video is processed in the background
  3. HLS stream becomes available at the video_url
  4. You'll receive a webhook notification when processing completes (if configured)

Video limits are measured in minutes of video, not file size.

Custom Object IDs

You can specify a custom object ID instead of using the auto-generated one:

javascript
formData.append('object', 'my-custom-id');

WARNING

Object IDs must be unique. If you use an existing ID, the upload will fail.

Organizing with Folders

Use folders to organize your files:

javascript
formData.append('folder', 'products/electronics');

Folders are stored in metadata and can be used for filtering (coming soon).

Best Practices

  1. Compress before upload: Pre-compress large images to save bandwidth
  2. Use meaningful tags: Tags make searching and filtering easier
  3. Organize with folders: Group related assets together
  4. Handle errors gracefully: Always check response status and handle errors

Next Steps

Released under the MIT License.