Archive API docs
    Jump to

    Narrow by kind with a prefix — q: queries, m: mutations, t: types, g: guides, f: fields and arguments.

    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.graphql
    query LabelsDefault {
      labels {
        name
        deleting
      }
    }
    Headers
    Authorization: Bearer docs_demo_token_0000000000000000000000
    WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a
    200 OK 22 lines
    {
      "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.

    query.graphql
    mutation CreateLabelsDefault($labelNames: [String!]!) {
      createLabels(labelNames: $labelNames) {
        labels {
          name
          deleting
        }
        userErrors {
          field
          message
        }
      }
    }
    variables.json
    {
      "labelNames": [
        "Holiday 2025",
        "vip"
      ]
    }
    Headers
    Authorization: Bearer docs_demo_token_0000000000000000000000
    WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a
    200 OK 17 lines
    {
      "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.

    query.graphql
    mutation AddLabelsToCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
      addLabelsToCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
        creators {
          id
          customAttributes
        }
        userErrors {
          field
          message
        }
      }
    }
    variables.json
    {
      "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
    200 OK 37 lines
    {
      "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 labels first 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 userErrors entry 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.
    query.graphql
    mutation RemoveLabelsFromCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
      removeLabelsFromCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
        creators {
          id
          customAttributes
        }
        userErrors {
          field
          message
        }
      }
    }
    variables.json
    {
      "creatorIds": [
        "5e6f95ca-070b-5cef-8e0c-08d855365419"
      ],
      "labelNames": [
        "vip"
      ]
    }
    Headers
    Authorization: Bearer docs_demo_token_0000000000000000000000
    WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a
    200 OK 21 lines
    {
      "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:

    CreatorsUsePrice
    1 to 40addLabelsToCreators / removeLabelsFromCreators50 + 100 × (n − 1)
    41 to 1,000bulkAddLabelsToCreators / bulkRemoveLabelsFromCreators250
    more than 1,000several bulk calls of up to 1,000 each250 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.

    query.graphql
    mutation BulkAddLabelsToCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
      bulkAddLabelsToCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
        scheduled
        operationId
        acceptedCount
        skippedCreatorIds
        userErrors {
          field
          message
        }
      }
    }
    variables.json
    {
      "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
    200 OK 11 lines
    {
      "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; acceptedCount is the number of creators scheduled. When no id resolves, nothing is scheduled and scheduled is false.
    • 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 userErrors entry 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.
    query.graphql
    mutation BulkRemoveLabelsFromCreatorsDefault($creatorIds: [ID!]!, $labelNames: [String!]!) {
      bulkRemoveLabelsFromCreators(creatorIds: $creatorIds, labelNames: $labelNames) {
        scheduled
        operationId
        acceptedCount
        skippedCreatorIds
        userErrors {
          field
          message
        }
      }
    }
    variables.json
    {
      "creatorIds": [
        "5e6f95ca-070b-5cef-8e0c-08d855365419"
      ],
      "labelNames": [
        "vip"
      ]
    }
    Headers
    Authorization: Bearer docs_demo_token_0000000000000000000000
    WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a
    200 OK 11 lines
    {
      "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.

    query.graphql
    mutation RenameLabelDefault($labelName: String!, $newLabelName: String!) {
      renameLabel(labelName: $labelName, newLabelName: $newLabelName) {
        scheduled
        operationId
        userErrors {
          field
          message
        }
      }
    }
    variables.json
    {
      "labelName": "Plant Parents",
      "newLabelName": "Plant Lovers"
    }
    Headers
    Authorization: Bearer docs_demo_token_0000000000000000000000
    WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a
    200 OK 9 lines
    {
      "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 in labels, 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.

    query.graphql
    mutation MergeLabelsDefault($labelName: String!, $intoLabelName: String!) {
      mergeLabels(labelName: $labelName, intoLabelName: $intoLabelName) {
        scheduled
        operationId
        userErrors {
          field
          message
        }
      }
    }
    variables.json
    {
      "labelName": "Retired 2024",
      "intoLabelName": "Gifting"
    }
    Headers
    Authorization: Bearer docs_demo_token_0000000000000000000000
    WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a
    200 OK 9 lines
    {
      "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 from labels, 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 as labelName in any spelling (use renameLabel to 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.

    query.graphql
    mutation DeleteLabelDefault($labelName: String!) {
      deleteLabel(labelName: $labelName) {
        scheduled
        operationId
        labelName
        userErrors {
          field
          message
        }
      }
    }
    variables.json
    {
      "labelName": "Retired 2024"
    }
    Headers
    Authorization: Bearer docs_demo_token_0000000000000000000000
    WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a
    200 OK 10 lines
    {
      "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: true with the operation already running. Repeated requests reuse that operation.
    • A name that matches no label is a userErrors entry on ["labelName"] with scheduled: false. The check is best effort: a deletion that another caller starts at the same moment can still answer scheduled: true.
    • labelName in 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:

    1. the user who authorized the OAuth token, if they are a member of the workspace;
    2. otherwise the person who issued the API token, if they are a member;
    3. otherwise the workspace’s creator, if they are still a member and not an Archive employee;
    4. 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.