curl --request POST \
--url https://api.orbit.devotel.io/api/v1/contacts/bulk/tag \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"contact_ids": [
"contact_01HZX8Y7QJK4M",
"contact_01HZX8Y7QJK4N"
],
"action": "add",
"tags": [
"vip",
"newsletter"
]
}
'import requests
url = "https://api.orbit.devotel.io/api/v1/contacts/bulk/tag"
payload = {
"contact_ids": ["contact_01HZX8Y7QJK4M", "contact_01HZX8Y7QJK4N"],
"action": "add",
"tags": ["vip", "newsletter"]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
contact_ids: ['contact_01HZX8Y7QJK4M', 'contact_01HZX8Y7QJK4N'],
action: 'add',
tags: ['vip', 'newsletter']
})
};
fetch('https://api.orbit.devotel.io/api/v1/contacts/bulk/tag', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.orbit.devotel.io/api/v1/contacts/bulk/tag",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'contact_ids' => [
'contact_01HZX8Y7QJK4M',
'contact_01HZX8Y7QJK4N'
],
'action' => 'add',
'tags' => [
'vip',
'newsletter'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.orbit.devotel.io/api/v1/contacts/bulk/tag"
payload := strings.NewReader("{\n \"contact_ids\": [\n \"contact_01HZX8Y7QJK4M\",\n \"contact_01HZX8Y7QJK4N\"\n ],\n \"action\": \"add\",\n \"tags\": [\n \"vip\",\n \"newsletter\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.orbit.devotel.io/api/v1/contacts/bulk/tag")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"contact_ids\": [\n \"contact_01HZX8Y7QJK4M\",\n \"contact_01HZX8Y7QJK4N\"\n ],\n \"action\": \"add\",\n \"tags\": [\n \"vip\",\n \"newsletter\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.orbit.devotel.io/api/v1/contacts/bulk/tag")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"contact_ids\": [\n \"contact_01HZX8Y7QJK4M\",\n \"contact_01HZX8Y7QJK4N\"\n ],\n \"action\": \"add\",\n \"tags\": [\n \"vip\",\n \"newsletter\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"data": {
"queued": false,
"affected": 2,
"failed": [],
"total": 2,
"partial": false
},
"meta": {
"request_id": "req_01HZX9ABCDEF",
"timestamp": "2026-02-02T12:00:00.000Z"
}
}{
"data": {
"queued": true,
"job_id": "bulkop_01HZX9QJK4M",
"total": 25000
},
"meta": {
"request_id": "req_01HZX9ABCDEF",
"timestamp": "2026-02-02T12:00:00.000Z"
}
}Bulk add or remove contact tags
Add or remove one or more tags across a selected audience in a single request. Target the audience with either an explicit contact_ids array or a filters segment AST — provide exactly one. Selections at or below the async threshold are applied inline and return the affected count; larger selections are queued and return a job id to poll via GET /contacts/bulk/jobs/.
curl --request POST \
--url https://api.orbit.devotel.io/api/v1/contacts/bulk/tag \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"contact_ids": [
"contact_01HZX8Y7QJK4M",
"contact_01HZX8Y7QJK4N"
],
"action": "add",
"tags": [
"vip",
"newsletter"
]
}
'import requests
url = "https://api.orbit.devotel.io/api/v1/contacts/bulk/tag"
payload = {
"contact_ids": ["contact_01HZX8Y7QJK4M", "contact_01HZX8Y7QJK4N"],
"action": "add",
"tags": ["vip", "newsletter"]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
contact_ids: ['contact_01HZX8Y7QJK4M', 'contact_01HZX8Y7QJK4N'],
action: 'add',
tags: ['vip', 'newsletter']
})
};
fetch('https://api.orbit.devotel.io/api/v1/contacts/bulk/tag', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.orbit.devotel.io/api/v1/contacts/bulk/tag",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'contact_ids' => [
'contact_01HZX8Y7QJK4M',
'contact_01HZX8Y7QJK4N'
],
'action' => 'add',
'tags' => [
'vip',
'newsletter'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.orbit.devotel.io/api/v1/contacts/bulk/tag"
payload := strings.NewReader("{\n \"contact_ids\": [\n \"contact_01HZX8Y7QJK4M\",\n \"contact_01HZX8Y7QJK4N\"\n ],\n \"action\": \"add\",\n \"tags\": [\n \"vip\",\n \"newsletter\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.orbit.devotel.io/api/v1/contacts/bulk/tag")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"contact_ids\": [\n \"contact_01HZX8Y7QJK4M\",\n \"contact_01HZX8Y7QJK4N\"\n ],\n \"action\": \"add\",\n \"tags\": [\n \"vip\",\n \"newsletter\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.orbit.devotel.io/api/v1/contacts/bulk/tag")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"contact_ids\": [\n \"contact_01HZX8Y7QJK4M\",\n \"contact_01HZX8Y7QJK4N\"\n ],\n \"action\": \"add\",\n \"tags\": [\n \"vip\",\n \"newsletter\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"data": {
"queued": false,
"affected": 2,
"failed": [],
"total": 2,
"partial": false
},
"meta": {
"request_id": "req_01HZX9ABCDEF",
"timestamp": "2026-02-02T12:00:00.000Z"
}
}{
"data": {
"queued": true,
"job_id": "bulkop_01HZX9QJK4M",
"total": 25000
},
"meta": {
"request_id": "req_01HZX9ABCDEF",
"timestamp": "2026-02-02T12:00:00.000Z"
}
}Authorizations
Dashboard JWT token from Clerk
Body
Whether to add or remove the given tag(s).
add, remove Explicit contact ids to target (max 50,000). Provide this OR filters, not both.
Segment filter AST resolving the audience server-side (up to 100,000 contacts). Provide this OR contact_ids.
A single tag to apply or remove (legacy form). Provide this OR tags.
50Up to 25 tags to apply or remove atomically. Provide this OR tag.
50