About
Roboflow lets you upload, annotate, inspect, tag, and delete individual images in a project through the REST API and the Python SDK. Use these operations when you need finer control than the bulk dataset upload flow provides - for example, uploading one image at a time from a stream, attaching annotations after the fact, or updating image metadata and tags. To bulk-import a whole labeled dataset at once, see Upload a Dataset.
HTTP API
The REST API exposes per-image operations for uploading images and annotations, fetching image details, deleting images, and managing image tags and metadata.
Upload an Image
Use the REST API to upload an image from a local file or URL.
When you pass image=, Roboflow fetches the URL from its own servers. If it cannot fetch the URL (bad URL, private address, or the host answers with a 4xx status), the response is a 400 with a message that starts with "Could not fetch image URL". If the host answers with a 5xx status or the connection fails, the response is a 502 with retryable: true, so you can send the same request again later.
Parameters
Querystring parameters accepted by the API:
api_key: Obtain from https://app.roboflow.com/account/api
image: [Optional] URL of the image to add. Use if your image is hosted elsewhere (Required when you don't POST a base64 encoded image in the request body).
name: [Optional] The filename of the image (if not set, we will try to infer it).
batch: [Optional] Group images under a batch with this name
tag: [Optional] Can be specified multiple times. Add tags to uploaded image.
split: [Optional] One of: train, valid, or test (defaults to train).
sequence_number: [Optional] If you want to keep the order of your images in the dataset, you can uploaded images increasing sequence numbers.
sequence_size: [Optional] The total number of images in the sequence. Defaults to 100,000 if not set.
inference_id: [Optional] The inference ID passed returned from a roboflow inference detection. This inference_id allows the image to be correlated with a roboflow detection in Model Monitoring (enterprise feature).
Linux or macOS
Uploading a local file called YOUR_IMAGE.jpg using multipart/form-data (recommended):
curl -F name=YOUR_IMAGE.jpg -F split=train \
-F file=@YOUR_IMAGE.jpg \
"https://api.roboflow.com/dataset/YOUR_DATASET_NAME/upload?\
api_key=$ROBOFLOW_API_KEY"Alternatively, uploading a base64 encoded image:
base64 -i YOUR_IMAGE.jpg | curl -d @- \
"https://api.roboflow.com/dataset/your-dataset/upload?\
api_key=$ROBOFLOW_API_KEY&\
name=YOUR_IMAGE.jpg&\
split=train&\
batch=BATCH_NAME_FOR_UPLOAD"Uploading an image hosted on the web via its URL (don't forget to URL encode it):
curl -X POST "https://api.roboflow.com/dataset/your-dataset/upload?\
api_key=$ROBOFLOW_API_KEY&\
image=https%3A%2F%2Fi.imgur.com%2FPEEvqPN.png&\
name=201-956-1246.png&\
split=train"Windows
You will need to install curl for Windows and GNU's base64 tool for Windows. The easiest way to do this is to use the git for Windows installer which also includes the curl and base64 command line tools when you select "Use Git and optional Unix tools from the Command Prompt" during installation.
Then you can use the same commands as above.
Node.js
We're using axios and form-data to perform the POST request in this example so first run npm install axios form-data to install the dependency.
Uploading with multipart/form-data (recommended):
const axios = require("axios");
const fs = require("fs");
const FormData = require('form-data');
const formData = new FormData();
formData.append("name", "YOUR_IMAGE.jpg");
formData.append("file", fs.createReadStream("YOUR_IMAGE.jpg"));
formData.append("split", "train");
axios({
method: "POST",
url: "https://api.roboflow.com/dataset/YOUR_DATASET_NAME/upload",
params: {
api_key: "YOUR_API_KEY"
},
data: formData,
headers: formData.getHeaders()
})
.then(function(response) {
console.log(response.data);
})
.catch(function(error) {
console.log(error.message);
});Uploading with base64 encoded image (not recommended):
const axios = require("axios");
const fs = require("fs");
const image = fs.readFileSync("YOUR_IMAGE.jpg", {
encoding: "base64"
});
axios({
method: "POST",
url: "https://api.roboflow.com/dataset/YOUR_DATASET_NAME/upload",
params: {
api_key: "YOUR_API_KEY",
name: "YOUR_IMAGE.jpg",
split: "train",
batch: "YOUR_BATCH_NAME"
},
data: image,
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
})
.then(function(response) {
console.log(response.data);
})
.catch(function(error) {
console.log(error.message);
});Adding an Image Hosted Elsewhere via URL
const axios = require("axios");
axios({
method: "POST",
url: "https://api.roboflow.com/dataset/YOUR_DATASET_NAME/upload",
params: {
api_key: "YOUR_API_KEY",
image: "https://i.imgur.com/PEEvqPN.png",
name: "201-956-1246.png",
split: "train"
}
})
.then(function(response) {
console.log(response.data);
})
.catch(function(error) {
console.log(error.message);
});Web
We are currently beta testing roboflow.js, a browser-based JavaScript library which, among other things, includes safe client-side uploads without exposing your secret API Key to the web. If you'd like early access, please contact us.
Swift
An example upload snippet using Swift for developing on iOS.
//Upload an image to a provided project
public func uploadImage(image: UIImage, project: String, completion: @escaping (UploadResult)->()) {
let encodedImage = convertImageToBase64String(img: image)
let uuid = UUID().uuidString
var request = URLRequest(url: URL(string: "https://api.roboflow.com/dataset/\(project)/upload?api_key=\(apiKey!)&name=\(uuid)&split=train")!,timeoutInterval: Double.infinity)
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = encodedImage.toData()
URLSession.shared.dataTask(with: request) { data, response, error in
// Parse Response to String
guard let data = data else {
completion(UploadResult.Error)
return
}
do {
let dict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
let duplicate = dict!["duplicate"] as? Bool
if duplicate == true {
completion(UploadResult.Duplicate)
} else {
let success = dict!["success"] as! Bool
if success == true {
completion(UploadResult.Success)
} else {
completion(UploadResult.Error)
}
}
} catch {
print(error.localizedDescription)
completion(UploadResult.Error)
}
}.resume()
}
func convertImageToBase64String (img: UIImage) -> String {
return img.jpegData(compressionQuality: 1)?.base64EncodedString() ?? ""
}}
Kotlin
Uploading with base64 encoded image:
import java.io.*
import java.net.HttpURLConnection
import java.net.URL
import java.nio.charset.StandardCharsets
import java.util.*
fun main() {
// Get Image Path
val filePath = System.getProperty("user.dir") + System.getProperty("file.separator") + "YOUR_IMAGE.jpg"
val file = File(filePath)
// Base 64 Encode
val encodedFile: String
val fileInputStreamReader = FileInputStream(file)
val bytes = ByteArray(file.length().toInt())
fileInputStreamReader.read(bytes)
encodedFile = String(Base64.getEncoder().encode(bytes), StandardCharsets.US_ASCII)
val API_KEY = "" // Your API Key
val DATASET_NAME = "your-dataset" // Set Dataset Name (Found in Dataset URL)
// Construct the URL
val uploadURL = "https://api.roboflow.com/dataset/" +
DATASET_NAME + "/upload" +
"?api_key=" + API_KEY +
"&name=YOUR_IMAGE.jpg" +
"&split=train"
// Http Request
var connection: HttpURLConnection? = null
try {
// Configure connection to URL
val url = URL(uploadURL)
connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded")
connection.setRequestProperty("Content-Length",
Integer.toString(encodedFile.toByteArray().size))
connection.setRequestProperty("Content-Language", "en-US")
connection.useCaches = false
connection.doOutput = true
//Send request
val wr = DataOutputStream(
connection.outputStream)
wr.writeBytes(encodedFile)
wr.close()
// Get Response
val stream = connection.inputStream
val reader = BufferedReader(InputStreamReader(stream))
var line: String?
while (reader.readLine().also { line = it } != null) {
println(line)
}
reader.close()
} catch (e: Exception) {
e.printStackTrace()
} finally {
connection?.disconnect()
}
}
main()Adding an Image Hosted Elsewhere via URL:
import java.io.BufferedReader
import java.io.DataOutputStream
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
fun main() {
val imageURL = "https://i.imgur.com/PEEvqPN.png" // Replace Image URL
val API_KEY = "" // Your API Key
val DATASET_NAME = "your-dataset" // Set Dataset Name (Found in Dataset URL)
// Upload URL
val uploadURL = ("https://api.roboflow.com/dataset/" + DATASET_NAME + "/upload" + "?api_key=" + API_KEY
+ "&name=YOUR_IMAGE.jpg" + "&split=train" + "&image="
+ URLEncoder.encode(imageURL, "utf-8"))
// Http Request
var connection: HttpURLConnection? = null
try {
// Configure connection to URL
val url = URL(uploadURL)
connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
connection.setRequestProperty("Content-Length", Integer.toString(uploadURL.toByteArray().size))
connection.setRequestProperty("Content-Language", "en-US")
connection.useCaches = false
connection.doOutput = true
// Send request
val wr = DataOutputStream(connection.outputStream)
wr.writeBytes(uploadURL)
wr.close()
// Get Response
val stream = connection.inputStream
val reader = BufferedReader(InputStreamReader(stream))
var line: String?
while (reader.readLine().also { line = it } != null) {
println(line)
}
reader.close()
} catch (e: Exception) {
e.printStackTrace()
} finally {
connection?.disconnect()
}
}
main()Android (Java)
Uploading with base64 encoded image:
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class UploadLocal {
public static void main(String[] args) throws IOException {
// Get Image Path
String filePath = System.getProperty("user.dir") + System.getProperty("file.separator") + "YOUR_IMAGE.jpg";
File file = new File(filePath);
// Base 64 Encode
String encodedFile;
FileInputStream fileInputStreamReader = new FileInputStream(file);
byte[] bytes = new byte[(int) file.length()];
fileInputStreamReader.read(bytes);
encodedFile = new String(Base64.getEncoder().encode(bytes), StandardCharsets.US_ASCII);
String API_KEY = ""; // Your API Key
String DATASET_NAME = "your-dataset"; // Set Dataset Name (Found in Dataset URL)
// Construct the URL
String uploadURL =
"https://api.roboflow.com/dataset/"+
DATASET_NAME + "/upload" +
"?api_key=" + API_KEY +
"&name=YOUR_IMAGE.jpg" +
"&split=train";
// Http Request
HttpURLConnection connection = null;
try {
//Configure connection to URL
URL url = new URL(uploadURL);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
connection.setRequestProperty("Content-Length",
Integer.toString(encodedFile.getBytes().length));
connection.setRequestProperty("Content-Language", "en-US");
connection.setUseCaches(false);
connection.setDoOutput(true);
//Send request
DataOutputStream wr = new DataOutputStream(
connection.getOutputStream());
wr.writeBytes(encodedFile);
wr.close();
// Get Response
InputStream stream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}Adding an Image Hosted Elsewhere via URL:
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class UploadHosted {
public static void main(String[] args) {
String imageURL = "https://i.imgur.com/PEEvqPN.png"; // Replace Image URL
String API_KEY = ""; // Your API Key
String DATASET_NAME = "your-dataset"; // Set Dataset Name (Found in Dataset URL)
// Upload URL
String uploadURL = "https://api.roboflow.com/dataset/" + DATASET_NAME + "/upload" + "?api_key=" + API_KEY
+ "&name=YOUR_IMAGE.jpg" + "&split=train" + "&image="
+ URLEncoder.encode(imageURL, StandardCharsets.UTF_8);
// Http Request
HttpURLConnection connection = null;
try {
// Configure connection to URL
URL url = new URL(uploadURL);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("Content-Length", Integer.toString(uploadURL.getBytes().length));
connection.setRequestProperty("Content-Language", "en-US");
connection.setUseCaches(false);
connection.setDoOutput(true);
// Send request
DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
wr.writeBytes(uploadURL);
wr.close();
// Get Response
InputStream stream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}Ruby
source "https://rubygems.org"
gem "httparty", "~> 0.18.1"
gem "base64", "~> 0.1.0"
gem "cgi", "~> 0.2.1"GEM
remote: https://rubygems.org/
specs:
base64 (0.1.0)
cgi (0.2.1)
httparty (0.18.1)
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2021.0225)
multi_xml (0.6.0)
PLATFORMS
x64-mingw32
x86_64-linux
DEPENDENCIES
base64 (~> 0.1.0)
cgi (~> 0.2.1)
httparty (~> 0.18.1)
BUNDLED WITH
2.2.15Uploading with base64 encoded image:
require 'base64'
require 'httparty'
encoded = Base64.encode64(File.open("YOUR_IMAGE.jpg", "rb").read)
dataset_name = "your-dataset" # Set Dataset Name (Found in Dataset URL)
api_key = "" # Your API KEY Here
params = "?api_key=" + api_key +
"&name=YOUR_IMAGE.jpg" +
"&split=train"
response = HTTParty.post(
"https://api.roboflow.com/dataset/" + dataset_name + "/upload" + params,
body: encoded,
headers: {
'Content-Type' => 'application/x-www-form-urlencoded',
'charset' => 'utf-8'
})
puts response
Adding an Image Hosted Elsewhere via URL:
require 'httparty'
require 'cgi'
dataset_name = "your-dataset" # Set Dataset Name (Found in Dataset URL)
api_key = "" # Your API KEY Here
img_url = "https://i.imgur.com/PEEvqPN.png" # Construct the URL
img_url = CGI::escape(img_url)
params = "?api_key=" + api_key +
"&name=YOUR_IMAGE.jpg" +
"&split=train" +
"&image=" + img_url
response = HTTParty.post(
"https://api.roboflow.com/dataset/" + dataset_name + "/upload" + params,
headers: {
'Content-Type' => 'application/x-www-form-urlencoded',
'charset' => 'utf-8'
})
puts responsePHP
Uploading with base64 encoded image:
<?php
// Base 64 Encode Image
$data = base64_encode(file_get_contents("YOUR_IMAGE.jpg"));
$api_key = ""; // Set API Key
$dataset_name = "your-dataset"; // Set Dataset Name (Found in Dataset URL)
// URL for Http Request
$url = "https://api.roboflow.com/dataset/"
. $dataset_name . "/upload"
. "?api_key=" . $api_key
. "&name=YOUR_IMAGE.jpg"
. "&split=train";
// Setup + Send Http request
$options = array(
'http' => array (
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => $data
));
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
echo $result;
?>Adding an Image Hosted Elsewhere via URL:
<?php
$api_key = ""; // Set API Key
$dataset_name = "your-dataset"; // Set Dataset Name (Found in Dataset URL)
$img_url = "https://i.imgur.com/PEEvqPN.png";
// URL for Http Request
$url = "https://api.roboflow.com/dataset/"
. $dataset_name . "/upload"
. "?api_key=" . $api_key
. "&name=YOUR_IMAGE.jpg"
. "&split=train"
. "&image=" . urlencode($img_url);
// Setup + Send Http request
$options = array(
'http' => array (
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST'
));
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
echo $result;
?>Go
Uploading with base64 encoded image:
package main
import (
"bufio"
"encoding/base64"
"fmt"
"io/ioutil"
"os"
"net/http"
"strings"
)
func main() {
api_key := "" // Your API Key
dataset_name := "Your-Dataset" // Set Dataset Name (Found in Dataset URL)
// Open file on disk.
f, _ := os.Open("YOUR_IMAGE.jpg")
// Read entire JPG into byte slice.
reader := bufio.NewReader(f)
content, _ := ioutil.ReadAll(reader)
// Encode as base64.
data := base64.StdEncoding.EncodeToString(content)
uploadURL := "https://api.roboflow.com/dataset/"+ dataset_name + "/upload"+
"?api_key=" + api_key +
"&name=YOUR_IMAGE.jpg" +
"&split=train"
res, _ := http.Post(uploadURL, "application/x-www-form-urlencoded", strings.NewReader(data))
body, _ := ioutil.ReadAll(res.Body)
fmt.Println(string(body))
}Adding an Image Hosted Elsewhere via URL:
package main
import (
"fmt"
"net/http"
"net/url"
"io/ioutil"
)
func main() {
api_key := "" // Your API Key
dataset_name := "Your-Dataset" // Set Dataset Name (Found in Dataset URL)
img_url := "https://i.imgur.com/PEEvqPN.png"
uploadURL := "https://api.roboflow.com/dataset/"+ dataset_name + "/upload"+
"?api_key=" + api_key +
"&name=YOUR_IMAGE.jpg" +
"&split=train" + "&image=" + url.QueryEscape(img_url)
res, _ := http.Post(uploadURL, "application/x-www-form-urlencoded", nil)
body, _ := ioutil.ReadAll(res.Body)
fmt.Println(string(body))
}.NET
Uploading with base64 encoded image:
using System;
using System.IO;
using System.Net;
using System.Text;
namespace UploadLocal
{
class UploadLocal
{
static void Main(string[] args)
{
byte[] imageArray = System.IO.File.ReadAllBytes(@"YOUR_IMAGE.jpg");
string encoded = Convert.ToBase64String(imageArray);
byte[] data = Encoding.ASCII.GetBytes(encoded);
string API_KEY = ""; // Your API Key
string DATASET_NAME = "your-dataset"; // Set Dataset Name (Found in Dataset URL)
// Construct the URL
string uploadURL =
"https://api.roboflow.com/dataset/" +
DATASET_NAME + "/upload" +
"?api_key=" + API_KEY +
"&name=YOUR_IMAGE.jpg" +
"&split=train";
// Service Request Config
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
// Configure Request
WebRequest request = WebRequest.Create(uploadURL);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = data.Length;
// Write Data
using (Stream stream = request.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
// Get Response
string responseContent = null;
using (WebResponse response = request.GetResponse())
{
using (Stream stream = response.GetResponseStream())
{
using (StreamReader sr99 = new StreamReader(stream))
{
responseContent = sr99.ReadToEnd();
}
}
}
Console.WriteLine(responseContent);
}
}
}Adding an Image Hosted Elsewhere via URL:
using System;
using System.IO;
using System.Net;
using System.Web;
namespace UploadHosted
{
class UploadHosted
{
static void Main(string[] args)
{
string API_KEY = ""; // Your API Key
string DATASET_NAME = "your-dataset"; // Set Dataset Name (Found in Dataset URL)
string imageURL = "https://i.imgur.com/PEEvqPN.png";
imageURL = HttpUtility.UrlEncode(imageURL);
// Construct the URL
string uploadURL =
"https://api.roboflow.com/dataset/" +
DATASET_NAME + "/upload" +
"?api_key=" + API_KEY +
"&name=YOUR_IMAGE.jpg" +
"&split=train" +
"&image=" + imageURL;
// Service Point Config
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
// Configure Http Request
WebRequest request = WebRequest.Create(uploadURL);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = 0;
// Get Response
string responseContent = null;
using (WebResponse response = request.GetResponse())
{
using (Stream stream = response.GetResponseStream())
{
using (StreamReader sr99 = new StreamReader(stream))
{
responseContent = sr99.ReadToEnd();
}
}
}
Console.WriteLine(responseContent);
}
}
}View Uploaded Images in Roboflow
Images uploaded via the API can be found in the Annotate tab, under the unassigned column and marked as uploaded via API.
If you specify a batch upload parameter, your image will still be found in the Annotate tab but instead of going to the uploaded via API batch it will be found in the batch you specified.
Upload an Annotation
If you already have existing annotations, along with your images, you can upload them to Roboflow.
This works with any of our supported annotation formats that use an annotation file that references the file name of the uploaded image.
Did you know? You can drag and drop (or select) the annotation files along with your images on the Upload page of the app without using the API.
Add prediction=true to save the annotation as a model prediction instead of ground truth. If the image is still in an upload batch and is not in an annotation job yet, Roboflow moves it into a Review job for that batch.
Add predictionRouting=unassigned to skip that move. The annotation still saves and the image is still marked as annotated, but it stays in its batch and stays unassigned. Use predictionRouting=review to ask for the default routing. Both values need prediction=true, and you cannot use them with jobName.
Example
Attaching a VOC XML annotation to an image with ID abc123 in the your-dataset dataset called YOUR_ANNOTATION.xml:
cat YOUR_ANNOTATION.xml | curl -d @- \
"https://api.roboflow.com/dataset/your-dataset/annotate/abc123?\
api_key=YOUR_KEY&\
name=YOUR_ANNOTATION.xml"Attaching a Darknet TXT annotation to an image with ID abc123 in the your-dataset dataset called YOUR_ANNOTATION.txt using a json labelmap - in this case we need to send the contents of the annotation file in a json instead of just sending it as the body.
#!/bin/bash
# store the annotation as a json-compatible string
txt_content=$(cat YOUR_ANNOTATION.txt | sed 's/\\/\\\\/g; s/"/\\"/g; s/$/\\n/' | tr -d '\n')
# create a json string with the annotation file and the label map [0=flower, 1=leaf]
json_payload="{ \"annotationFile\": \"$txt_content\", \"labelmap\":{\"0\":\"flower\", \"1\":\"leaf\"} }"
# upload the annotation + labelmap
echo $json_payload | curl -H "Content-Type: application/json" -d @- \
"https://api.roboflow.com/dataset/cultura-pepino-dark/annotate/abc123?\
api_key=YOUR_KEY&\
name=YOUR_ANNOTATION.txt"We're using axios to perform the POST request in this example so first run npm install axios to install the dependency.
Uploading a Local Image
const axios = require("axios");
const fs = require("fs");
const filename = "YOUR_ANNOTATION.xml";
const annotation = fs.readFileSync(filename, "utf-8");
axios({
method: "POST",
url: "https://api.roboflow.com/dataset/your-dataset/annotate/abc123",
params: {
api_key: "YOUR_KEY",
name: filename
},
data: annotation,
headers: {
"Content-Type": "text/plain"
}
})
.then(function(response) {
console.log(response.data);
})
.catch(function(error) {
console.log(error.message);
});Kotlin
Uploading with base64 encoded image:
import java.io.*
import java.net.HttpURLConnection
import java.net.URL
import java.nio.charset.StandardCharsets
import java.util.*
fun main() {
// Get Image Path
val filePath = System.getProperty("user.dir") + System.getProperty("file.separator") + "YOUR_IMAGE.jpg"
val file = File(filePath)
// Base 64 Encode
val encodedFile: String
val fileInputStreamReader = FileInputStream(file)
val bytes = ByteArray(file.length().toInt())
fileInputStreamReader.read(bytes)
encodedFile = String(Base64.getEncoder().encode(bytes), StandardCharsets.US_ASCII)
val API_KEY = "" // Your API Key
val DATASET_NAME = "your-dataset" // Set Dataset Name (Found in Dataset URL)
// Construct the URL
val uploadURL = "https://api.roboflow.com/dataset/" +
DATASET_NAME + "/upload" +
"?api_key=" + API_KEY +
"&name=YOUR_IMAGE.jpg" +
"&split=train"
// Http Request
var connection: HttpURLConnection? = null
try {
// Configure connection to URL
val url = URL(uploadURL)
connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded")
connection.setRequestProperty("Content-Length",
Integer.toString(encodedFile.toByteArray().size))
connection.setRequestProperty("Content-Language", "en-US")
connection.useCaches = false
connection.doOutput = true
//Send request
val wr = DataOutputStream(
connection.outputStream)
wr.writeBytes(encodedFile)
wr.close()
// Get Response
val stream = connection.inputStream
val reader = BufferedReader(InputStreamReader(stream))
var line: String?
while (reader.readLine().also { line = it } != null) {
println(line)
}
reader.close()
} catch (e: Exception) {
e.printStackTrace()
} finally {
connection?.disconnect()
}
}
main()Adding an Image Hosted Elsewhere via URL:
import java.io.BufferedReader
import java.io.DataOutputStream
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
fun main() {
val imageURL = "https://i.imgur.com/PEEvqPN.png" // Replace Image URL
val API_KEY = "" // Your API Key
val DATASET_NAME = "your-dataset" // Set Dataset Name (Found in Dataset URL)
// Upload URL
val uploadURL = ("https://api.roboflow.com/dataset/" + DATASET_NAME + "/upload" + "?api_key=" + API_KEY
+ "&name=YOUR_IMAGE.jpg" + "&split=train" + "&image="
+ URLEncoder.encode(imageURL, "utf-8"))
// Http Request
var connection: HttpURLConnection? = null
try {
// Configure connection to URL
val url = URL(uploadURL)
connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
connection.setRequestProperty("Content-Length", Integer.toString(uploadURL.toByteArray().size))
connection.setRequestProperty("Content-Language", "en-US")
connection.useCaches = false
connection.doOutput = true
// Send request
val wr = DataOutputStream(connection.outputStream)
wr.writeBytes(uploadURL)
wr.close()
// Get Response
val stream = connection.inputStream
val reader = BufferedReader(InputStreamReader(stream))
var line: String?
while (reader.readLine().also { line = it } != null) {
println(line)
}
reader.close()
} catch (e: Exception) {
e.printStackTrace()
} finally {
connection?.disconnect()
}
}
main()Android (Java)
Uploading with base64 encoded image:
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class UploadLocal {
public static void main(String[] args) throws IOException {
// Get Image Path
String filePath = System.getProperty("user.dir") + System.getProperty("file.separator") + "YOUR_IMAGE.jpg";
File file = new File(filePath);
// Base 64 Encode
String encodedFile;
FileInputStream fileInputStreamReader = new FileInputStream(file);
byte[] bytes = new byte[(int) file.length()];
fileInputStreamReader.read(bytes);
encodedFile = new String(Base64.getEncoder().encode(bytes), StandardCharsets.US_ASCII);
String API_KEY = ""; // Your API Key
String DATASET_NAME = "your-dataset"; // Set Dataset Name (Found in Dataset URL)
// Construct the URL
String uploadURL =
"https://api.roboflow.com/dataset/"+
DATASET_NAME + "/upload" +
"?api_key=" + API_KEY +
"&name=YOUR_IMAGE.jpg" +
"&split=train";
// Http Request
HttpURLConnection connection = null;
try {
//Configure connection to URL
URL url = new URL(uploadURL);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
connection.setRequestProperty("Content-Length",
Integer.toString(encodedFile.getBytes().length));
connection.setRequestProperty("Content-Language", "en-US");
connection.setUseCaches(false);
connection.setDoOutput(true);
//Send request
DataOutputStream wr = new DataOutputStream(
connection.getOutputStream());
wr.writeBytes(encodedFile);
wr.close();
// Get Response
InputStream stream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}Adding an Image Hosted Elsewhere via URL:
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class UploadHosted {
public static void main(String[] args) {
String imageURL = "https://i.imgur.com/PEEvqPN.png"; // Replace Image URL
String API_KEY = ""; // Your API Key
String DATASET_NAME = "your-dataset"; // Set Dataset Name (Found in Dataset URL)
// Upload URL
String uploadURL = "https://api.roboflow.com/dataset/" + DATASET_NAME + "/upload" + "?api_key=" + API_KEY
+ "&name=YOUR_IMAGE.jpg" + "&split=train" + "&image="
+ URLEncoder.encode(imageURL, StandardCharsets.UTF_8);
// Http Request
HttpURLConnection connection = null;
try {
// Configure connection to URL
URL url = new URL(uploadURL);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("Content-Length", Integer.toString(uploadURL.getBytes().length));
connection.setRequestProperty("Content-Language", "en-US");
connection.setUseCaches(false);
connection.setDoOutput(true);
// Send request
DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
wr.writeBytes(uploadURL);
wr.close();
// Get Response
InputStream stream = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}Get Details About an Image
You can fetch details of a specific image using the REST API.
To fetch details of a specific image, make a GET request to the following API endpoint.
https://api.roboflow.com/:workspace/:project/images/:image_idHere is an example request to the API to fetch the details of an image
curl -X GET "https://api.roboflow.com/my-workspace/my-project-name/images/image-id?api_key=$ROBOFLOW_API_KEY" \
-H 'Content-Type: application/json'This endpoint returns a JSON object containing the following information about the image:
{
"image":
"id": string,
"name": string,
"annotation": {
"key": string,
"width": number,
"height": number,
"boxes": Array<{
"label": string,
"x": number,
"y": number,
"width": number,
"height": number
}>
},
"labels": string[],
"split": string,
"tags": string[],
"created": number,
"urls": {
"original": string,
"thumb": string,
"annotation": string
},
"embedding": number[]
}
}Delete an Image from a Dataset
You can remove images from a Dataset using the REST API.
To remove images from a Dataset, make a DELETE request to the following API endpoint, passing the image IDs in the endpoint.
https://api.roboflow.com/:workspace/:project/imagesHere is an example request to the API to remove images
curl "https://api.roboflow.com/my-workspace/my-project/images?api_key=$ROBOFLOW_API_KEY" \
-X DELETE \
-H "Content-Type: application/json" \
-d '{"images": ["1", "2"]}'This endpoint returns a 204 status if the operation was successful.
List, Add, and Remove Image Tags
You can assign tags to specific images on Roboflow using the REST API
To add, remove, and set tags to images hosted on Roboflow, make POST request to the following API endpoint. Use the Search API to retrieve the image ID associated with the image name:
https://api.roboflow.com/:workspace/:project/images/:image_id/tagsHere is an example request to the API (can "add", "remove", or "set" a tag):
curl -X POST "https://api.roboflow.com/my-workspace/my-project-name/images/image-id/tags?api_key=$ROBOFLOW_API_KEY" \
-H 'Content-Type: application/json' \
--data \
'{
"operation": "add",
"tags": "image_tag_test"
}'This endpoint accepts the following values in the POST body:
{
// // options are ["add", "remove", "set"]
"operation": string,
// array of strings of the tags
"tags": string[],
}The API will add the tag to the specified image in Roboflow (remember to pass in the image ID to the post request and not the image name).
Update Image Metadata and Tags
You can write custom metadata and tags to images in your workspace using the REST API. There are two endpoints: one for updating a single image synchronously, and one for updating up to 1,000 images in a batch.
Both endpoints require an API key with the image:tag scope.
Request Body
Both endpoints accept the same fields (the batch endpoint wraps them in an updates array):
- imageId (string) - required for batch update in body, inferred ffrom the path in single update
- metadata (object) - Key-value pairs to set on the image's user metadata.
- removeMetadata (string[]) - Metadata keys to delete from the image.
- addTags (string[]) - Tags to add to the image.
- removeTags (string[]) - Tags to remove from the image.You must include at least one of these fields. You cannot set and remove the same metadata key or tag in the same request. Metadata keys that start with _rf_internal_ are reserved for Roboflow and are rejected.
Single Image
Update metadata and tags for a single image.
POST https://api.roboflow.com/:workspace/images/:image/metadata?api_key=YOUR_API_KEYExample
curl -X POST "https://api.roboflow.com/my-workspace/images/abc123/metadata?api_key=$ROBOFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"metadata": { "camera": "front", "blur_score": 0.8 },
"addTags": ["reviewed"]
}'Response
{
"success": true
}Batch Update
Update metadata and tags for multiple images asynchronously. Accepts up to 1,000 images per request.
POST https://api.roboflow.com/:workspace/images/metadata?api_key=YOUR_API_KEYExample
curl -X POST "https://api.roboflow.com/my-workspace/images/metadata?api_key=$ROBOFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"updates": [
{
"imageId": "abc123",
"metadata": { "camera": "front" },
"addTags": ["reviewed"]
},
{
"imageId": "def456",
"removeTags": ["needs-review"]
}
]
}'Response
Returns 202 with a task ID. Poll the task URL to check progress.
{
"taskId": "task-id-here",
"url": "https://api.roboflow.com/my-workspace/asynctasks/task-id-here"
}See Async Tasks for how to poll the task status.
Errors
- 400 - Empty body, malformed field types, or conflicting fields (e.g. same key in both metadata and removeMetadata).
- 404 - Image not found in the workspace (single image endpoint only).Python SDK
Project exposes the per-image operations that complement the bulk upload_dataset flow. Use these when you need finer control over single-image uploads, want to attach annotations after the fact, or are ingesting images one-at-a-time from a stream.
Upload an image (with optional annotation)
Project.upload() is the high-level "do the right thing" helper. It accepts a single image plus an optional matching annotation file and ships both to the project in one call.
import roboflow
rf = roboflow.Roboflow(api_key="YOUR_API_KEY")
project = rf.workspace().project("my-detector")
result = project.upload(
image_path="./photo.jpg",
annotation_path="./photo.xml", # optional; matched VOC / COCO / etc. annotation
split="train", # train | valid | test
batch_name="ingest-2026-05", # optional; groups uploads in the web UI
tag_names=["camera-A", "indoor"], # optional; apply tags
is_prediction=False, # set True for model-generated annotations awaiting review
num_retry_uploads=2, # retries on transient upload failures
)
print(result)single_upload() is a lower-level variant that takes the same arguments and returns the raw API responses for both the image and (if provided) the annotation.
Upload an image only
project.upload_image(
image_path="./photo.jpg",
split="train",
batch_name="ingest-2026-05",
tag_names=["camera-A"],
)Useful when annotations don't exist yet and the image goes straight to a labeler.
Validate an image before uploading
check_valid_image() runs Roboflow's local size / format checks without hitting the API:
if project.check_valid_image("./photo.jpg"):
project.upload_image("./photo.jpg")Attach an annotation to an existing image
save_annotation() posts an annotation against an image that's already in the project. Useful for adding labels created elsewhere, or for promoting a model prediction to ground truth.
project.save_annotation(
image_id="<image-id>",
annotation_path="./photo.xml",
is_prediction=False,
annotation_overwrite=True, # replace any existing annotation
)Pass annotation_labelmap="./labelmap.yaml" to map class indices into class names if your annotation format requires it.
Saving with is_prediction=True can move the image into a review job. See where predictions land.
Fetch an image's metadata
info = project.image("<image-id>")
print(info["name"], info["split"], info["annotations"])Returns image metadata, current split, and annotation status.
Delete images
Project-level (only deletes images that belong to this project):
project.delete_images(["<image-id-1>", "<image-id-2>"])Workspace-level (removes images regardless of which projects reference them - use with care):
workspace.delete_images(["<image-id-1>", "<image-id-2>"])A note on uploads in v1.3.6+
As of roboflow 1.3.6, the SDK uploads the original image bytes rather than re-encoding via Pillow. This restores parity with the web uploader and lets the Roboflow server deduplicate uploads by SHA-256. If you have automation that uploads the same image twice (e.g. to add it to multiple batches), you'll see the second upload succeed without consuming additional storage credits.
CLI
Use the CLI to upload one image and its optional annotation, tags, or metadata.
roboflow image upload photo.jpg -p PROJECT_IDOptions
| Flag | Description |
|---|---|
-p, --project | Project ID (required) |
-a, --annotation | Path to an annotation file |
-m, --labelmap | Path to a label map file |
-s, --split | Dataset split: train, valid, or test. The default is train. |
-t, --tag | Comma-separated tag names |
-M, --metadata | Metadata as a JSON string |
--is-prediction | Mark the upload as a prediction |
-b, --batch | Batch name |
Examples
Upload an image with an annotation:
roboflow image upload photo.jpg -p my-project -a annotation.xml -s validUpload an image with tags and metadata:
roboflow image upload photo.jpg -p my-project -t "outdoor,daytime" -M '{"camera_id":"cam001"}'To upload a directory or .zip archive, see Upload a Dataset.
MCP Server
Connect your AI agent to the MCP Server and it can find and update images with these tools:
| Tool | Description |
|---|---|
images_search | Search for images inside a project. |
images_update_metadata | Update metadata and tags on a single image. |
images_batch_update_metadata | Batch-update metadata and tags on multiple images. |
annotations_save | Save an annotation for an existing image. |