Labels
Labels are the tags your team puts on creators in the Archive app. The API works on the same taxonomy: a label you create here shows up in the app, and a label applied in the app is listed here. Labels have no id in this API. A label is its name.
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 spelling the workspace stored, the one someone typed first, and you
should pass that spelling back as it is.
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 put it on anyone. A blank name, an over-long name or a name that is being
deleted refuses the whole call with 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, and write synchronously. Both return the creators as stored. 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": []
}
}
} - Labels are set membership. Assigning a label a creator already carries, or removing one it does not carry, succeeds and changes nothing, so 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 not a creator of this workspace, the whole call is refused with a
userErrorsentry on["creatorIds"]naming it, and nothing is written. - 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 also share a per-workspace label-write limit: a workspace labels at most
40 creators at once, then one more creator every 2 seconds, across every caller and both entry
points. Past it the call fails with 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 whatever the number of creators and do not draw from the label-write limit. Pick by 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 |
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. - The same set-membership rules apply: repeating a call is safe, assigning creates a missing label, and removing leaves the label 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 follows the new spelling. The rename runs in the background. scheduled: true comes with
the operationId of the run; nothing has changed yet when the call returns.
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 folds one label into another existing label across the whole
workspace: every creator carrying labelName ends up carrying intoLabelName, and labelName is
deleted. A creator that already carried both keeps one. 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. Afterwards nothing tells the two labels’ creators apart, and the merged
label is gone. One call merges one 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. Every caller converges on one deletion per label. - 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 draw from the label-write
limit. An agent should confirm with the user before it calls
deleteLabel (echoing the label name), renameLabel (echoing both names and the automation caveat)
or mergeLabels (echoing both names, the irreversibility and the automation caveat), and treat
scheduled: true as queued, not done. A busy collision is an isError result to retry.