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

    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": []
        }
      }
    }
    • 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 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 not a creator of this workspace, the whole call is refused with a userErrors entry 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.
    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 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:

    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
    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.
    • The same set-membership rules apply: repeating a call is safe, assigning creates a missing label, and removing leaves the label 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 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.

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

    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. 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 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. Every caller converges on one deletion per label.
    • 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 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.