Labels
Labels organize creators in your workspace. The API and Archive app share the same labels: changes made in either place appear in both. Labels have no ID in this API. Use the label’s name to identify it.
Label names are the identity
Every label argument takes a name, and matching ignores case and surrounding whitespace: vip,
VIP and Vip all name the same label. The API never stores a second spelling of a label that
already exists. It returns the stored spelling. Use that exact spelling in later requests.
A label with deleting: true is being removed by a workspace-wide deletion. Do not assign it, and
do not create it again until it disappears from the list.
List labels
labels returns every label in the workspace, sorted by name ignoring case. The
list is not paginated and holds at most 1,000 labels.
query LabelsDefault {
labels {
name
deleting
}
} Authorization: Bearer docs_demo_token_0000000000000000000000
WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a curl https://app.archive.com/api/v2 \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer docs_demo_token_0000000000000000000000' \
-H 'WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a' \
-d '{
"query": "query LabelsDefault {\n labels {\n name\n deleting\n }\n}"
}' const response = await fetch("https://app.archive.com/api/v2", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a"
},
body: JSON.stringify({
query: `query LabelsDefault {
labels {
name
deleting
}
}`
})
})
console.log(JSON.stringify(await response.json(), null, 2)) require "json"
require "net/http"
query = <<~'GRAPHQL'
query LabelsDefault {
labels {
name
deleting
}
}
GRAPHQL
response = Net::HTTP.post(
URI("https://app.archive.com/api/v2"),
{ query: query }.to_json,
"Content-Type" => "application/json",
"Authorization" => "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID" => "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a"
)
puts response.body import json
import requests
query = """
query LabelsDefault {
labels {
name
deleting
}
}
"""
response = requests.post(
"https://app.archive.com/api/v2",
json={"query": query},
headers={
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a",
},
)
print(json.dumps(response.json(), indent=2)) {
"data": {
"labels": [
{
"name": "Gifting",
"deleting": false
},
{
"name": "Plant Parents",
"deleting": false
},
{
"name": "Retired 2024",
"deleting": false
},
{
"name": "VIP",
"deleting": false
}
]
}
} Create labels
createLabels adds up to 50 names per call, each at most 100
characters. A name the workspace already has is not an error: it comes back with its stored
spelling, so repeating the call is safe. In the example vip resolves to the stored VIP, and only
Holiday 2025 is new.
mutation CreateLabelsDefault($labelNames: [String!]!) {
createLabels(labelNames: $labelNames) {
labels {
name
deleting
}
userErrors {
field
message
}
}
} {
"labelNames": [
"Holiday 2025",
"vip"
]
} Authorization: Bearer docs_demo_token_0000000000000000000000
WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a curl https://app.archive.com/api/v2 \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer docs_demo_token_0000000000000000000000' \
-H 'WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a' \
-d '{
"query": "mutation CreateLabelsDefault($labelNames: [String!]!) {\n createLabels(labelNames: $labelNames) {\n labels {\n name\n deleting\n }\n userErrors {\n field\n message\n }\n }\n}",
"variables": {
"labelNames": [
"Holiday 2025",
"vip"
]
}
}' const response = await fetch("https://app.archive.com/api/v2", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a"
},
body: JSON.stringify({
query: `mutation CreateLabelsDefault($labelNames: [String!]!) {
createLabels(labelNames: $labelNames) {
labels {
name
deleting
}
userErrors {
field
message
}
}
}`,
variables: {
"labelNames": [
"Holiday 2025",
"vip"
]
}
})
})
console.log(JSON.stringify(await response.json(), null, 2)) require "json"
require "net/http"
query = <<~'GRAPHQL'
mutation CreateLabelsDefault($labelNames: [String!]!) {
createLabels(labelNames: $labelNames) {
labels {
name
deleting
}
userErrors {
field
message
}
}
}
GRAPHQL
response = Net::HTTP.post(
URI("https://app.archive.com/api/v2"),
{ query: query, variables: { "labelNames" => ["Holiday 2025", "vip"] } }.to_json,
"Content-Type" => "application/json",
"Authorization" => "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID" => "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a"
)
puts response.body import json
import requests
query = """
mutation CreateLabelsDefault($labelNames: [String!]!) {
createLabels(labelNames: $labelNames) {
labels {
name
deleting
}
userErrors {
field
message
}
}
}
"""
response = requests.post(
"https://app.archive.com/api/v2",
json={"query": query, "variables": {"labelNames": ["Holiday 2025", "vip"]}},
headers={
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a",
},
)
print(json.dumps(response.json(), indent=2)) {
"data": {
"createLabels": {
"labels": [
{
"name": "Holiday 2025",
"deleting": false
},
{
"name": "VIP",
"deleting": false
}
],
"userErrors": []
}
}
} Creating a label does not assign it to any creator. A blank name, an overlong name, or a name being
deleted rejects the whole request. The response contains a userErrors entry on ["labelNames"], and nothing is created.
Assign and remove
addLabelsToCreators and
removeLabelsFromCreators take up to 40 creator ids and up
to 50 label names. Both finish the update before responding and return the updated creators. For more than 40
creators, use the bulk mutations.
mutation AddLabelsToCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
addLabelsToCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
creators {
id
customAttributes
}
userErrors {
field
message
}
}
} {
"creatorIds": [
"7750875f-6037-5751-a493-86ac5fc3e9c9",
"75fd5375-7f33-5331-a6cf-86e0c697c6f6"
],
"labelNames": [
"Gifting"
]
} Authorization: Bearer docs_demo_token_0000000000000000000000
WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a curl https://app.archive.com/api/v2 \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer docs_demo_token_0000000000000000000000' \
-H 'WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a' \
-d '{
"query": "mutation AddLabelsToCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {\n addLabelsToCreators(creatorIds: $creatorIds, labelNames: $labelNames) {\n creators {\n id\n customAttributes\n }\n userErrors {\n field\n message\n }\n }\n}",
"variables": {
"creatorIds": [
"7750875f-6037-5751-a493-86ac5fc3e9c9",
"75fd5375-7f33-5331-a6cf-86e0c697c6f6"
],
"labelNames": [
"Gifting"
]
}
}' const response = await fetch("https://app.archive.com/api/v2", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a"
},
body: JSON.stringify({
query: `mutation AddLabelsToCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
addLabelsToCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
creators {
id
customAttributes
}
userErrors {
field
message
}
}
}`,
variables: {
"creatorIds": [
"7750875f-6037-5751-a493-86ac5fc3e9c9",
"75fd5375-7f33-5331-a6cf-86e0c697c6f6"
],
"labelNames": [
"Gifting"
]
}
})
})
console.log(JSON.stringify(await response.json(), null, 2)) require "json"
require "net/http"
query = <<~'GRAPHQL'
mutation AddLabelsToCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
addLabelsToCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
creators {
id
customAttributes
}
userErrors {
field
message
}
}
}
GRAPHQL
response = Net::HTTP.post(
URI("https://app.archive.com/api/v2"),
{ query: query, variables: { "creatorIds" => ["7750875f-6037-5751-a493-86ac5fc3e9c9", "75fd5375-7f33-5331-a6cf-86e0c697c6f6"], "labelNames" => ["Gifting"] } }.to_json,
"Content-Type" => "application/json",
"Authorization" => "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID" => "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a"
)
puts response.body import json
import requests
query = """
mutation AddLabelsToCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
addLabelsToCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
creators {
id
customAttributes
}
userErrors {
field
message
}
}
}
"""
response = requests.post(
"https://app.archive.com/api/v2",
json={"query": query, "variables": {"creatorIds": ["7750875f-6037-5751-a493-86ac5fc3e9c9", "75fd5375-7f33-5331-a6cf-86e0c697c6f6"], "labelNames": ["Gifting"]}},
headers={
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a",
},
)
print(json.dumps(response.json(), indent=2)) {
"data": {
"addLabelsToCreators": {
"creators": [
{
"id": "75fd5375-7f33-5331-a6cf-86e0c697c6f6",
"customAttributes": {
"age": null,
"emails": [],
"gender": "45b18b20-e86d-5c85-831c-19ada8104b25",
"labels": [
"Gifting"
],
"location": "United States",
"full_name": null,
"phone_numbers": []
}
},
{
"id": "7750875f-6037-5751-a493-86ac5fc3e9c9",
"customAttributes": {
"age": null,
"emails": [],
"gender": "52e6750a-79ca-5f7e-9352-228c7b9cb18f",
"labels": [
"Gifting"
],
"location": "United States",
"full_name": null,
"phone_numbers": []
}
}
],
"userErrors": []
}
}
} - Assigning a label the creator already has, or removing one they do not have, succeeds without changing anything. Both calls are safe to repeat.
- Assigning creates a missing label. Check
labelsfirst when you mean an existing label: a typo adds a new one. Names you assign are capped at 100 characters. - If any creator ID is outside this workspace or unknown, Archive rejects the whole request.
A
userErrorsentry on["creatorIds"]identifies it. No creators are changed. - Removing a label leaves the label itself in the workspace. Names you remove have no length cap, so any existing label can be removed.
mutation RemoveLabelsFromCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
removeLabelsFromCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
creators {
id
customAttributes
}
userErrors {
field
message
}
}
} {
"creatorIds": [
"5e6f95ca-070b-5cef-8e0c-08d855365419"
],
"labelNames": [
"vip"
]
} Authorization: Bearer docs_demo_token_0000000000000000000000
WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a curl https://app.archive.com/api/v2 \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer docs_demo_token_0000000000000000000000' \
-H 'WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a' \
-d '{
"query": "mutation RemoveLabelsFromCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {\n removeLabelsFromCreators(creatorIds: $creatorIds, labelNames: $labelNames) {\n creators {\n id\n customAttributes\n }\n userErrors {\n field\n message\n }\n }\n}",
"variables": {
"creatorIds": [
"5e6f95ca-070b-5cef-8e0c-08d855365419"
],
"labelNames": [
"vip"
]
}
}' const response = await fetch("https://app.archive.com/api/v2", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a"
},
body: JSON.stringify({
query: `mutation RemoveLabelsFromCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
removeLabelsFromCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
creators {
id
customAttributes
}
userErrors {
field
message
}
}
}`,
variables: {
"creatorIds": [
"5e6f95ca-070b-5cef-8e0c-08d855365419"
],
"labelNames": [
"vip"
]
}
})
})
console.log(JSON.stringify(await response.json(), null, 2)) require "json"
require "net/http"
query = <<~'GRAPHQL'
mutation RemoveLabelsFromCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
removeLabelsFromCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
creators {
id
customAttributes
}
userErrors {
field
message
}
}
}
GRAPHQL
response = Net::HTTP.post(
URI("https://app.archive.com/api/v2"),
{ query: query, variables: { "creatorIds" => ["5e6f95ca-070b-5cef-8e0c-08d855365419"], "labelNames" => ["vip"] } }.to_json,
"Content-Type" => "application/json",
"Authorization" => "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID" => "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a"
)
puts response.body import json
import requests
query = """
mutation RemoveLabelsFromCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
removeLabelsFromCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
creators {
id
customAttributes
}
userErrors {
field
message
}
}
}
"""
response = requests.post(
"https://app.archive.com/api/v2",
json={"query": query, "variables": {"creatorIds": ["5e6f95ca-070b-5cef-8e0c-08d855365419"], "labelNames": ["vip"]}},
headers={
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a",
},
)
print(json.dumps(response.json(), indent=2)) {
"data": {
"removeLabelsFromCreators": {
"creators": [
{
"id": "5e6f95ca-070b-5cef-8e0c-08d855365419",
"customAttributes": {
"age": null,
"emails": [],
"gender": "45b18b20-e86d-5c85-831c-19ada8104b25",
"labels": [],
"location": "United States",
"full_name": null,
"phone_numbers": []
}
}
],
"userErrors": []
}
}
} To verify an assignment, query creator(id:) with customAttributes and read
its labels key. customAttributes is a JSON value, so labels is a key inside it, not a field you
can select.
What a synchronous call costs
A synchronous assignment is priced per creator: 50 points for the first creator plus 100 for each
further one, so 40 creators cost 3,950 points plus the payload fields you select. A call that
costs more than your plan’s whole rate-limit capacity is refused with a
userErrors entry on ["creatorIds"] naming the largest call your plan allows, and nothing is
written or charged.
Synchronous label writes share a workspace limit: 40 creators at once, then one more creator every 2 seconds.
All callers and both mutations share this allowance. Exceeding it returns HTTP 429,
RateLimit-Policy: "label_writes";q=40;w=80 and a
Retry-After header. Wait that long, then retry.
Assign and remove in bulk
bulkAddLabelsToCreators and
bulkRemoveLabelsFromCreators take up to 1,000
creator ids and the same label names, and do the work in the background. They cost a flat 250
points regardless of the number of creators. They do not use the synchronous label-write allowance.
For an explicit list of creator IDs, choose by batch size:
| Creators | Use | Price |
|---|---|---|
| 1 to 40 | addLabelsToCreators / removeLabelsFromCreators | 50 + 100 × (n − 1) |
| 41 to 1,000 | bulkAddLabelsToCreators / bulkRemoveLabelsFromCreators | 250 |
| more than 1,000 | several bulk calls of up to 1,000 each | 250 per call |
You can also select creators with filter, customAttributeConditions, or both, as in the
creators query. Omit creatorIds when using filters.
Sending both an ID list and filters, or sending neither, returns INVALID_INPUT.
Filter selection has no creator-count cap; narrow a large selection if the request times out.
acceptedCount is the number of matches when the request is scheduled.
The background operation updates the creators matching when it runs, so that set can change.
Check operationRecords for the final results. A filter with no matches returns scheduled: false.
mutation BulkAddLabelsToCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
bulkAddLabelsToCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
scheduled
operationId
acceptedCount
skippedCreatorIds
userErrors {
field
message
}
}
} {
"creatorIds": [
"7750875f-6037-5751-a493-86ac5fc3e9c9",
"75fd5375-7f33-5331-a6cf-86e0c697c6f6"
],
"labelNames": [
"Plant Parents"
]
} Authorization: Bearer docs_demo_token_0000000000000000000000
WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a curl https://app.archive.com/api/v2 \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer docs_demo_token_0000000000000000000000' \
-H 'WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a' \
-d '{
"query": "mutation BulkAddLabelsToCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {\n bulkAddLabelsToCreators(creatorIds: $creatorIds, labelNames: $labelNames) {\n scheduled\n operationId\n acceptedCount\n skippedCreatorIds\n userErrors {\n field\n message\n }\n }\n}",
"variables": {
"creatorIds": [
"7750875f-6037-5751-a493-86ac5fc3e9c9",
"75fd5375-7f33-5331-a6cf-86e0c697c6f6"
],
"labelNames": [
"Plant Parents"
]
}
}' const response = await fetch("https://app.archive.com/api/v2", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a"
},
body: JSON.stringify({
query: `mutation BulkAddLabelsToCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
bulkAddLabelsToCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
scheduled
operationId
acceptedCount
skippedCreatorIds
userErrors {
field
message
}
}
}`,
variables: {
"creatorIds": [
"7750875f-6037-5751-a493-86ac5fc3e9c9",
"75fd5375-7f33-5331-a6cf-86e0c697c6f6"
],
"labelNames": [
"Plant Parents"
]
}
})
})
console.log(JSON.stringify(await response.json(), null, 2)) require "json"
require "net/http"
query = <<~'GRAPHQL'
mutation BulkAddLabelsToCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
bulkAddLabelsToCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
scheduled
operationId
acceptedCount
skippedCreatorIds
userErrors {
field
message
}
}
}
GRAPHQL
response = Net::HTTP.post(
URI("https://app.archive.com/api/v2"),
{ query: query, variables: { "creatorIds" => ["7750875f-6037-5751-a493-86ac5fc3e9c9", "75fd5375-7f33-5331-a6cf-86e0c697c6f6"], "labelNames" => ["Plant Parents"] } }.to_json,
"Content-Type" => "application/json",
"Authorization" => "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID" => "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a"
)
puts response.body import json
import requests
query = """
mutation BulkAddLabelsToCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
bulkAddLabelsToCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
scheduled
operationId
acceptedCount
skippedCreatorIds
userErrors {
field
message
}
}
}
"""
response = requests.post(
"https://app.archive.com/api/v2",
json={"query": query, "variables": {"creatorIds": ["7750875f-6037-5751-a493-86ac5fc3e9c9", "75fd5375-7f33-5331-a6cf-86e0c697c6f6"], "labelNames": ["Plant Parents"]}},
headers={
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a",
},
)
print(json.dumps(response.json(), indent=2)) {
"data": {
"bulkAddLabelsToCreators": {
"scheduled": true,
"operationId": "bb437896-b6cb-5c2e-9d2b-a838b77bf025",
"acceptedCount": 2,
"skippedCreatorIds": [],
"userErrors": []
}
}
} - An id that is not a creator of this workspace does not refuse the call. It is returned in
skippedCreatorIds, and the rest are scheduled;acceptedCountis the number of creators scheduled. When no id resolves, nothing is scheduled andscheduledisfalse. - At most 2 label bulk operations can be queued or processing in a workspace at once, counting
bulk label edits made in the Archive app. A third call is refused with a
userErrorsentry on["base"]that names the operations in flight. Wait for one of them to finish, then retry. - As with synchronous updates, assigning a label twice does not duplicate it, and removing an absent label changes nothing. Assignment creates missing labels; removal keeps the labels themselves in the workspace.
mutation BulkRemoveLabelsFromCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
bulkRemoveLabelsFromCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
scheduled
operationId
acceptedCount
skippedCreatorIds
userErrors {
field
message
}
}
} {
"creatorIds": [
"5e6f95ca-070b-5cef-8e0c-08d855365419"
],
"labelNames": [
"vip"
]
} Authorization: Bearer docs_demo_token_0000000000000000000000
WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a curl https://app.archive.com/api/v2 \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer docs_demo_token_0000000000000000000000' \
-H 'WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a' \
-d '{
"query": "mutation BulkRemoveLabelsFromCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {\n bulkRemoveLabelsFromCreators(creatorIds: $creatorIds, labelNames: $labelNames) {\n scheduled\n operationId\n acceptedCount\n skippedCreatorIds\n userErrors {\n field\n message\n }\n }\n}",
"variables": {
"creatorIds": [
"5e6f95ca-070b-5cef-8e0c-08d855365419"
],
"labelNames": [
"vip"
]
}
}' const response = await fetch("https://app.archive.com/api/v2", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a"
},
body: JSON.stringify({
query: `mutation BulkRemoveLabelsFromCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
bulkRemoveLabelsFromCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
scheduled
operationId
acceptedCount
skippedCreatorIds
userErrors {
field
message
}
}
}`,
variables: {
"creatorIds": [
"5e6f95ca-070b-5cef-8e0c-08d855365419"
],
"labelNames": [
"vip"
]
}
})
})
console.log(JSON.stringify(await response.json(), null, 2)) require "json"
require "net/http"
query = <<~'GRAPHQL'
mutation BulkRemoveLabelsFromCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
bulkRemoveLabelsFromCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
scheduled
operationId
acceptedCount
skippedCreatorIds
userErrors {
field
message
}
}
}
GRAPHQL
response = Net::HTTP.post(
URI("https://app.archive.com/api/v2"),
{ query: query, variables: { "creatorIds" => ["5e6f95ca-070b-5cef-8e0c-08d855365419"], "labelNames" => ["vip"] } }.to_json,
"Content-Type" => "application/json",
"Authorization" => "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID" => "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a"
)
puts response.body import json
import requests
query = """
mutation BulkRemoveLabelsFromCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
bulkRemoveLabelsFromCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
scheduled
operationId
acceptedCount
skippedCreatorIds
userErrors {
field
message
}
}
}
"""
response = requests.post(
"https://app.archive.com/api/v2",
json={"query": query, "variables": {"creatorIds": ["5e6f95ca-070b-5cef-8e0c-08d855365419"], "labelNames": ["vip"]}},
headers={
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a",
},
)
print(json.dumps(response.json(), indent=2)) {
"data": {
"bulkRemoveLabelsFromCreators": {
"scheduled": true,
"operationId": "05034d30-4102-538c-b292-a592d0b9feda",
"acceptedCount": 1,
"skippedCreatorIds": [],
"userErrors": []
}
}
} scheduled: true comes with the operationId of the run. Poll operation(id:)
until status is COMPLETED, PARTIAL or FAILED, read any per-creator failures with
operationRecords, then spot-check a creator with creator(id:). Bulk
label operations appear in operations with operationType
update_custom_attributes (add) or remove_custom_attributes (remove), carry one record per
creator, and send no operation.completed webhook.
Rename a label
renameLabel renames one label across the whole workspace: every creator
carrying it gets the new spelling. The rename runs in the background.
scheduled: true means the operation was queued, not that it finished. Use the returned operationId to check progress.
mutation RenameLabelDefault($labelName: String!, $newLabelName: String!) {
renameLabel(labelName: $labelName, newLabelName: $newLabelName) {
scheduled
operationId
userErrors {
field
message
}
}
} {
"labelName": "Plant Parents",
"newLabelName": "Plant Lovers"
} Authorization: Bearer docs_demo_token_0000000000000000000000
WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a curl https://app.archive.com/api/v2 \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer docs_demo_token_0000000000000000000000' \
-H 'WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a' \
-d '{
"query": "mutation RenameLabelDefault($labelName: String!, $newLabelName: String!) {\n renameLabel(labelName: $labelName, newLabelName: $newLabelName) {\n scheduled\n operationId\n userErrors {\n field\n message\n }\n }\n}",
"variables": {
"labelName": "Plant Parents",
"newLabelName": "Plant Lovers"
}
}' const response = await fetch("https://app.archive.com/api/v2", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a"
},
body: JSON.stringify({
query: `mutation RenameLabelDefault($labelName: String!, $newLabelName: String!) {
renameLabel(labelName: $labelName, newLabelName: $newLabelName) {
scheduled
operationId
userErrors {
field
message
}
}
}`,
variables: {
"labelName": "Plant Parents",
"newLabelName": "Plant Lovers"
}
})
})
console.log(JSON.stringify(await response.json(), null, 2)) require "json"
require "net/http"
query = <<~'GRAPHQL'
mutation RenameLabelDefault($labelName: String!, $newLabelName: String!) {
renameLabel(labelName: $labelName, newLabelName: $newLabelName) {
scheduled
operationId
userErrors {
field
message
}
}
}
GRAPHQL
response = Net::HTTP.post(
URI("https://app.archive.com/api/v2"),
{ query: query, variables: { "labelName" => "Plant Parents", "newLabelName" => "Plant Lovers" } }.to_json,
"Content-Type" => "application/json",
"Authorization" => "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID" => "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a"
)
puts response.body import json
import requests
query = """
mutation RenameLabelDefault($labelName: String!, $newLabelName: String!) {
renameLabel(labelName: $labelName, newLabelName: $newLabelName) {
scheduled
operationId
userErrors {
field
message
}
}
}
"""
response = requests.post(
"https://app.archive.com/api/v2",
json={"query": query, "variables": {"labelName": "Plant Parents", "newLabelName": "Plant Lovers"}},
headers={
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a",
},
)
print(json.dumps(response.json(), indent=2)) {
"data": {
"renameLabel": {
"scheduled": true,
"operationId": "fb352d7d-aafd-544a-93f8-eed5bf50ad39",
"userErrors": []
}
}
} The new name is at most 100 characters. Renaming onto a different label that already exists is
refused, as there is no implicit merge (use mergeLabels for that); a new
spelling of the same label (VIP to vip) is allowed. Each refusal is a userErrors entry with
scheduled: false, on the input at fault:
["labelName"]— the label does not exist, is being deleted, is stored under several spellings, or is already being renamed (wait until the first rename shows inlabels, then retry); or the workspace’s labels are busy with another change (retry shortly).["newLabelName"]— the new name is blank, too long, being deleted, or the name of another label.["base"]— the workspace has no user to attribute the rename to.
Merge two labels
mergeLabels combines two existing labels across the workspace.
Every creator with labelName gets intoLabelName, then Archive deletes labelName.
A creator that already had both keeps only the target label. The merge runs in the background;
scheduled: true comes with the operationId of the run.
mutation MergeLabelsDefault($labelName: String!, $intoLabelName: String!) {
mergeLabels(labelName: $labelName, intoLabelName: $intoLabelName) {
scheduled
operationId
userErrors {
field
message
}
}
} {
"labelName": "Retired 2024",
"intoLabelName": "Gifting"
} Authorization: Bearer docs_demo_token_0000000000000000000000
WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a curl https://app.archive.com/api/v2 \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer docs_demo_token_0000000000000000000000' \
-H 'WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a' \
-d '{
"query": "mutation MergeLabelsDefault($labelName: String!, $intoLabelName: String!) {\n mergeLabels(labelName: $labelName, intoLabelName: $intoLabelName) {\n scheduled\n operationId\n userErrors {\n field\n message\n }\n }\n}",
"variables": {
"labelName": "Retired 2024",
"intoLabelName": "Gifting"
}
}' const response = await fetch("https://app.archive.com/api/v2", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a"
},
body: JSON.stringify({
query: `mutation MergeLabelsDefault($labelName: String!, $intoLabelName: String!) {
mergeLabels(labelName: $labelName, intoLabelName: $intoLabelName) {
scheduled
operationId
userErrors {
field
message
}
}
}`,
variables: {
"labelName": "Retired 2024",
"intoLabelName": "Gifting"
}
})
})
console.log(JSON.stringify(await response.json(), null, 2)) require "json"
require "net/http"
query = <<~'GRAPHQL'
mutation MergeLabelsDefault($labelName: String!, $intoLabelName: String!) {
mergeLabels(labelName: $labelName, intoLabelName: $intoLabelName) {
scheduled
operationId
userErrors {
field
message
}
}
}
GRAPHQL
response = Net::HTTP.post(
URI("https://app.archive.com/api/v2"),
{ query: query, variables: { "labelName" => "Retired 2024", "intoLabelName" => "Gifting" } }.to_json,
"Content-Type" => "application/json",
"Authorization" => "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID" => "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a"
)
puts response.body import json
import requests
query = """
mutation MergeLabelsDefault($labelName: String!, $intoLabelName: String!) {
mergeLabels(labelName: $labelName, intoLabelName: $intoLabelName) {
scheduled
operationId
userErrors {
field
message
}
}
}
"""
response = requests.post(
"https://app.archive.com/api/v2",
json={"query": query, "variables": {"labelName": "Retired 2024", "intoLabelName": "Gifting"}},
headers={
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a",
},
)
print(json.dumps(response.json(), indent=2)) {
"data": {
"mergeLabels": {
"scheduled": true,
"operationId": "d0e2a7ec-c713-539e-bcc7-ae6fe33da461",
"userErrors": []
}
}
} A merge is irreversible. Afterward, label membership no longer shows which creators had the source label.
One call merges one source label into one target. Each refusal is a userErrors entry with
scheduled: false, on the input at fault:
["labelName"]— the label does not exist, is being deleted, is stored under several spellings, or is already being merged (wait until it disappears fromlabels, then retry); or the workspace’s labels are busy with another change (retry shortly).["intoLabelName"]— the target is blank, does not exist (a merge never creates a label), is being deleted, is stored under several spellings, or is the same label aslabelNamein any spelling (userenameLabelto respell a label).["base"]— the workspace has no user to attribute the merge to.
Delete a label
deleteLabel permanently removes one label from the workspace and from
every creator that carries it. The creators are kept. Deletion is irreversible and runs in the
background.
mutation DeleteLabelDefault($labelName: String!) {
deleteLabel(labelName: $labelName) {
scheduled
operationId
labelName
userErrors {
field
message
}
}
} {
"labelName": "Retired 2024"
} Authorization: Bearer docs_demo_token_0000000000000000000000
WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a curl https://app.archive.com/api/v2 \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer docs_demo_token_0000000000000000000000' \
-H 'WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a' \
-d '{
"query": "mutation DeleteLabelDefault($labelName: String!) {\n deleteLabel(labelName: $labelName) {\n scheduled\n operationId\n labelName\n userErrors {\n field\n message\n }\n }\n}",
"variables": {
"labelName": "Retired 2024"
}
}' const response = await fetch("https://app.archive.com/api/v2", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a"
},
body: JSON.stringify({
query: `mutation DeleteLabelDefault($labelName: String!) {
deleteLabel(labelName: $labelName) {
scheduled
operationId
labelName
userErrors {
field
message
}
}
}`,
variables: {
"labelName": "Retired 2024"
}
})
})
console.log(JSON.stringify(await response.json(), null, 2)) require "json"
require "net/http"
query = <<~'GRAPHQL'
mutation DeleteLabelDefault($labelName: String!) {
deleteLabel(labelName: $labelName) {
scheduled
operationId
labelName
userErrors {
field
message
}
}
}
GRAPHQL
response = Net::HTTP.post(
URI("https://app.archive.com/api/v2"),
{ query: query, variables: { "labelName" => "Retired 2024" } }.to_json,
"Content-Type" => "application/json",
"Authorization" => "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID" => "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a"
)
puts response.body import json
import requests
query = """
mutation DeleteLabelDefault($labelName: String!) {
deleteLabel(labelName: $labelName) {
scheduled
operationId
labelName
userErrors {
field
message
}
}
}
"""
response = requests.post(
"https://app.archive.com/api/v2",
json={"query": query, "variables": {"labelName": "Retired 2024"}},
headers={
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a",
},
)
print(json.dumps(response.json(), indent=2)) {
"data": {
"deleteLabel": {
"scheduled": true,
"operationId": "d6d1a270-cb9a-57fc-8c4c-e101c51e79d5",
"labelName": "Retired 2024",
"userErrors": []
}
}
} - The label name has no length cap, so a label of any length can be deleted.
- Deleting a label that is already being deleted returns
scheduled: truewith the operation already running. Repeated requests reuse that operation. - A name that matches no label is a
userErrorsentry on["labelName"]withscheduled: false. The check is best effort: a deletion that another caller starts at the same moment can still answerscheduled: true. labelNamein the payload echoes the name you sent, not the stored spelling.
Until the deletion finishes the label stays in labels, showing deleting: true once the
background run has marked it, and it may still appear on creators and in search.
Follow a delete, rename or merge
Delete, rename and merge return an operationId. Poll operation(id:) until
status is COMPLETED or FAILED, then confirm with labels: after a rename the new spelling is
listed and the old one is gone; after a merge or a delete the source label is gone.
A label operation reports progress through status, total and processed only. Its record counts
are always 0 and its item-id lists always empty; that is not a failure. Label operations, including
ones started in the app, appear in operations with operationType
delete_label, rename_label or merge_labels. They send no operation.completed webhook.
Busy and retry
A label write that collides with another write to the same labels is not a userErrors entry. It
fails with a GraphQL error carrying extensions.code SERVICE_UNAVAILABLE, and your creators are
unchanged. A delete whose background run could not be queued fails the same way, but its operation
already exists. Retry after a short wait; a retried delete resumes that same deletion. See
errors.
Attribution
Assignments, removals (synchronous or bulk), renames, merges and deletions are recorded as made by a person in your workspace:
- the user who authorized the OAuth token, if they are a member of the workspace;
- otherwise the person who issued the API token, if they are a member;
- otherwise the workspace’s creator, if they are still a member and not an Archive employee;
- otherwise the workspace’s earliest-joined member who is not an Archive employee.
If none of them applies, the request is refused with a userErrors entry on ["base"]. Creating a
label needs no user.
From an MCP client
The nine MCP tools take the same arguments and return the same payloads:
getLabels, createLabels,
addLabelsToCreators,
removeLabelsFromCreators,
bulkAddLabelsToCreators,
bulkRemoveLabelsFromCreators,
renameLabel, mergeLabels and
deleteLabel. They charge the same prices and have the same
limits as the GraphQL mutations. renameLabel and mergeLabels do not use the label-write allowance.
Before a destructive or workspace-wide change, an agent should confirm the details with the user:
deleteLabel: name the label to delete.renameLabel: give both names and explain the effect on campaign automations.mergeLabels: give the source and target names, explain that merging is irreversible, and mention affected automations.
Treat scheduled: true as queued, not completed. A conflict with another label write returns an isError result; wait briefly before retrying.