> For the complete documentation index, see [llms.txt](https://docs.roboflow.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.roboflow.com/datasets/manage/manage-images.md).

# Manage Images

Upload, annotate, inspect, tag, and delete individual images through the REST API and Python SDK.

## 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](/datasets/create-and-upload/upload-a-dataset.md).

## 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.

{% tabs %}
{% tab title="cURL" %}

#### 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:

```bash
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](https://www.urlencoder.org/)):

```bash
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](https://curl.se/windows/) and [GNU's base64 tool for Windows](http://gnuwin32.sourceforge.net/packages/coreutils.htm). The easiest way to do this is to use the [git for Windows installer](https://git-scm.com/downloads) 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.
{% endtab %}

{% tab title="JavaScript" %}

#### Node.js

We're using [axios](https://github.com/axios/axios) and [form-data](https://github.com/form-data/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):**

```javascript
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):**

```javascript
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**

```javascript
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](https://roboflow.com/contact).
{% endtab %}

{% tab title="Swift" %}

#### Swift

An example upload snippet using Swift for developing on iOS.

```swift
//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() ?? ""
}
```

}
{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="Android & Java" %}

#### Kotlin

#### **Uploading with base64 encoded image:**

```kotlin
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:**

```kotlin
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:**

```java
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:**

```java
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();
            }
        }
    }
}
```

{% endtab %}

{% tab title="Ruby" %}

#### **Ruby**

{% code title="Gemfile" %}

```ruby
source "https://rubygems.org"

gem "httparty", "~> 0.18.1"
gem "base64", "~> 0.1.0"
gem "cgi", "~> 0.2.1"
```

{% endcode %}

{% code title="Gemfile.lock" %}

```ruby
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.15
```

{% endcode %}

#### **Uploading with base64 encoded image:**

```ruby
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:**

```ruby
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 response
```

{% endtab %}

{% tab title="PHP" %}

#### **PHP**

#### **Uploading with base64 encoded image:**

```php
<?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
<?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;
?>
```

{% endtab %}

{% tab title="Go" %}

#### **Go**

#### **Uploading with base64 encoded image:**

```go
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:**

```go
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))


}
```

{% endtab %}

{% tab title=".NET" %}

#### **.NET**

#### **Uploading with base64 encoded image:**

```csharp
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:**

```csharp
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);

        }
    }
}
```

{% endtab %}
{% endtabs %}

#### 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.

{% hint style="success" %}
This works with any of [our supported annotation formats](http://roboflow.com/formats) that use an annotation file that references the file name of the uploaded image.
{% endhint %}

{% hint style="info" %}
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.
{% endhint %}

#### Query Parameters

The annotate endpoint reads these query parameters.

<table data-search="false"><thead><tr><th width="190">Name</th><th>Description</th></tr></thead><tbody><tr><td><code>api_key</code></td><td>Your Roboflow API key. Required.</td></tr><tr><td><code>name</code></td><td>The filename of the annotation file, with its extension. Roboflow reads the annotation format from it. Required.</td></tr><tr><td><code>addToDataset</code></td><td>Adds the image to the Dataset, so the next dataset version can include it. Defaults to <code>true</code>. Send <code>addToDataset=false</code> to save the annotation without changing Dataset membership, for example while the image still waits in a batch.</td></tr><tr><td><code>split</code></td><td>Sets the image split: <code>train</code>, <code>valid</code> or <code>test</code> (<code>val</code> and <code>validation</code> both mean <code>valid</code>). Omit it to keep the split the image has. It does not control Dataset membership.</td></tr><tr><td><code>overwrite</code></td><td>Send <code>overwrite=true</code> to replace an annotation the image already has. Without it the request fails with a 409. Saves in classification projects always replace.</td></tr><tr><td><code>jobName</code></td><td>Moves the image into the annotation job with this name, and creates that job if it does not exist. Omit it to leave the image in the batch or job it is in.</td></tr><tr><td><code>prediction</code></td><td>Send <code>prediction=true</code> to save the annotation as a model prediction instead of ground truth. A prediction upload never adds the image to the Dataset.</td></tr><tr><td><code>predictionRouting</code></td><td>Needs <code>prediction=true</code>, and you cannot send it with <code>jobName</code>. Use <code>review</code> to move the image out of its batch and into a Review job, which only applies when the image is in a batch of this project and in no job of it. <code>unassigned</code> is the default and leaves batch and job membership alone.</td></tr></tbody></table>

{% hint style="warning" %}
`addToDataset` defaults to `true`. An upload that does not send it adds the image to the Dataset, which takes the image out of the batch, job and review flow. Send `addToDataset=false` when you want the image to stay in that flow.
{% endhint %}

#### Videos

In an Action Recognition Project, use the video ID in place of the image ID and send one [`roboflow-video-coco` document](/datasets/create-and-upload/adding-data.md#import-video-segment-annotations) with the video's segments as the body. `addToDataset`, `split`, and `overwrite` work as they do for images, and a video added without a `split` goes to `train`. The video stays in its batch. New labels become classes unless the Project locks its classes. `prediction`, `predictionRouting`, and `jobName` return a 400 error.

{% tabs %}
{% tab title="cURL" %}

#### Example

Attaching a [VOC XML annotation](https://roboflow.com/formats/pascal-voc-xml) to an image with ID `abc123` in the `your-dataset` dataset called `YOUR_ANNOTATION.xml`:

```bash
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](https://roboflow.com/formats/yolo-darknet-txt) 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.

```bash
#!/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"
```

{% endtab %}

{% tab title="JavaScript" %}
We're using [axios](https://github.com/axios/axios) to perform the POST request in this example so first run `npm install axios` to install the dependency.

**Uploading a Local Image**

```javascript
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);
});
```

{% endtab %}

{% tab title="Android and Java" %}

#### Kotlin

#### **Uploading with base64 encoded image:**

```kotlin
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:**

```kotlin
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:**

```java
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:**

```java
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();
            }
        }
    }
}
```

{% endtab %}
{% endtabs %}

### 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.

```url
https://api.roboflow.com/:workspace/:project/images/:image_id
```

Here is an example request to the API to fetch the details of an image

```bash
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:

```typescript
{
    "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,
            "video": string // videos only
        },
        "mediaType": "image" | "video",
        "segments": object[], // videos only
        "embedding": number[]
    }
}
```

For a video, `urls.original` and `urls.thumb` hold its poster image, `urls.video` is a short-lived download link, and `segments` lists its [action segments](/datasets/annotate/annotate/annotate-video-segments.md). `annotation` is `null`, and the `format` query parameter returns a 400 error.

### Delete an Image from a Dataset

You can remove images from a Dataset using the REST API.

{% tabs %}
{% tab title="REST API" %}
To remove images from a Dataset, make a DELETE request to the following API endpoint, passing the image IDs in the endpoint.

```url
https://api.roboflow.com/:workspace/:project/images
```

Here is an example request to the API to remove images

```bash
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.
{% endtab %}
{% endtabs %}

### List, Add, and Remove Image Tags

You can assign tags to specific images on Roboflow using the REST API

{% tabs %}
{% tab title="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:

```url
https://api.roboflow.com/:workspace/:project/images/:image_id/tags
```

Here is an example request to the API (can "add", "remove", or "set" a tag):

```bash
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:

```json
{
     // // 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).
{% endtab %}
{% endtabs %}

### 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_KEY
```

**Example**

```bash
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**

```json
{
    "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_KEY
```

**Example**

```bash
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.

```json
{
    "taskId": "task-id-here",
    "url": "https://api.roboflow.com/my-workspace/asynctasks/task-id-here"
}
```

See [Async Tasks](https://docs.roboflow.com/reference/platform/rest-api/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`](/datasets/create-and-upload/upload-a-dataset.md#python-sdk) 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.

```python
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

```python
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:

```python
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.

```python
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](/datasets/create-and-upload/upload-a-dataset.md#where-predictions-land).

### Fetch an image's metadata

```python
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):

```python
project.delete_images(["<image-id-1>", "<image-id-2>"])
```

Workspace-level (removes images regardless of which projects reference them - use with care):

```python
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.

```bash
roboflow image upload photo.jpg -p PROJECT_ID
```

### Options

<table data-search="false"><thead><tr><th>Flag</th><th>Description</th></tr></thead><tbody><tr><td><code>-p</code>, <code>--project</code></td><td>Project ID (required)</td></tr><tr><td><code>-a</code>, <code>--annotation</code></td><td>Path to an annotation file</td></tr><tr><td><code>-m</code>, <code>--labelmap</code></td><td>Path to a label map file</td></tr><tr><td><code>-s</code>, <code>--split</code></td><td>Dataset split: <code>train</code>, <code>valid</code>, or <code>test</code>. The default is <code>train</code>.</td></tr><tr><td><code>-t</code>, <code>--tag</code></td><td>Comma-separated tag names</td></tr><tr><td><code>-M</code>, <code>--metadata</code></td><td>Metadata as a JSON string</td></tr><tr><td><code>--is-prediction</code></td><td>Mark the upload as a prediction</td></tr><tr><td><code>-b</code>, <code>--batch</code></td><td>Batch name</td></tr></tbody></table>

### Examples

Upload an image with an annotation:

```bash
roboflow image upload photo.jpg -p my-project -a annotation.xml -s valid
```

Upload an image with tags and metadata:

```bash
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](/datasets/create-and-upload/upload-a-dataset.md).

## MCP Server

Connect your AI agent to the [MCP Server](https://docs.roboflow.com/agents/mcp-server) and it can find and update images with these tools:

<table data-search="false"><thead><tr><th width="290">Tool</th><th>Description</th></tr></thead><tbody><tr><td><code>images_search</code></td><td>Search for images inside a project.</td></tr><tr><td><code>images_update_metadata</code></td><td>Update metadata and tags on a single image.</td></tr><tr><td><code>images_batch_update_metadata</code></td><td>Batch-update metadata and tags on multiple images.</td></tr><tr><td><code>annotations_save</code></td><td>Save an annotation for an existing image.</td></tr></tbody></table>
