Importing creators
importCreators adds creators to your workspace from their social
handles. It works like Creators → Import from file in the Archive app.
Archive matches each entry to an existing creator or creates a creator from the supplied handles.
It applies the labels you send and creates any missing labels.
The import runs in the background. Keep the returned operationId to check progress and read each creator’s result.
Send a batch
Each request accepts 1 to 250 creators. Each creator has 1 to 5 handles, each a
{ provider, handle } pair, plus optional labels:
providerisINSTAGRAM,TIKTOKorYOUTUBE.handleis the creator’s handle on that platform, with or without a leading@, or their profile URL there.labelsholds up to 20 distinct label names per creator, each up to 100 characters (counted in Unicode code points). Names are trimmed, and blank or exactly repeated names are ignored. Names that differ only in case each count toward the 20-name cap.customAttributes(optional) sets custom attribute values. Use the keys returned bycustomAttributeSchemas(entity: CREATOR), with values in the formats below.
| Attribute type | Value to send |
|---|---|
TEXT, URL, EMAIL, PHONE | A string. PHONE must contain a phone number. |
NUMBER | A number |
BOOLEAN | true or false |
DATE, DATETIME | An ISO 8601 string |
A matching _LIST type | An array of the corresponding values |
SINGLE_SELECT_V3 | An option id |
MULTIPLE_SELECT_V2 | A non-empty array of option IDs |
A TEXT attribute with the ad code format accepts a TikTok Spark code or an Instagram ad code.
An option cannot be imported if another option in the same attribute has the same name, even in a different group.
Other attribute types and the labels custom attribute key are rejected; send labels through the separate labels input.
Values merge with an existing creator’s attributes. List values are added to the stored list, and null values are ignored.
A request that exceeds any limit is rejected as a whole: nothing is imported, and a
userErrors entry says why. When the cap belongs to one creator, its field points at that
creator, for example ["creators", "3", "labels"]. An unknown custom attribute key, or a value
that does not fit its schema, is refused the same way on
["creators", "<index>", "customAttributes", "<key>"], with a message naming the creator’s index
and the key.
mutation ImportCreatorsDefault($creators: [CreatorImportInput!]!, $operationId: ID) {
importCreators(creators: $creators, operationId: $operationId) {
operationId
acceptedCount
skipped {
index
provider
handle
reason
}
userErrors {
field
message
}
}
} {
"creators": [
{
"handles": [
{
"provider": "INSTAGRAM",
"handle": "@maya.greenhouse"
},
{
"provider": "TIKTOK",
"handle": "mayagreenhouse"
}
],
"labels": [
"Q4 seeding"
]
},
{
"handles": [
{
"provider": "YOUTUBE",
"handle": "fernandfrondstudio"
}
]
},
{
"handles": [
{
"provider": "INSTAGRAM",
"handle": "not a handle!"
}
]
}
],
"operationId": "3f1c9a52-7d4e-4b8a-9c21-6e5f0a7b8d13"
} 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 ImportCreatorsDefault($creators: [CreatorImportInput!]!, $operationId: ID) {\n importCreators(creators: $creators, operationId: $operationId) {\n operationId\n acceptedCount\n skipped {\n index\n provider\n handle\n reason\n }\n userErrors {\n field\n message\n }\n }\n}",
"variables": {
"creators": [
{
"handles": [
{
"provider": "INSTAGRAM",
"handle": "@maya.greenhouse"
},
{
"provider": "TIKTOK",
"handle": "mayagreenhouse"
}
],
"labels": [
"Q4 seeding"
]
},
{
"handles": [
{
"provider": "YOUTUBE",
"handle": "fernandfrondstudio"
}
]
},
{
"handles": [
{
"provider": "INSTAGRAM",
"handle": "not a handle!"
}
]
}
],
"operationId": "3f1c9a52-7d4e-4b8a-9c21-6e5f0a7b8d13"
}
}' 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 ImportCreatorsDefault($creators: [CreatorImportInput!]!, $operationId: ID) {
importCreators(creators: $creators, operationId: $operationId) {
operationId
acceptedCount
skipped {
index
provider
handle
reason
}
userErrors {
field
message
}
}
}`,
variables: {
"creators": [
{
"handles": [
{
"provider": "INSTAGRAM",
"handle": "@maya.greenhouse"
},
{
"provider": "TIKTOK",
"handle": "mayagreenhouse"
}
],
"labels": [
"Q4 seeding"
]
},
{
"handles": [
{
"provider": "YOUTUBE",
"handle": "fernandfrondstudio"
}
]
},
{
"handles": [
{
"provider": "INSTAGRAM",
"handle": "not a handle!"
}
]
}
],
"operationId": "3f1c9a52-7d4e-4b8a-9c21-6e5f0a7b8d13"
}
})
})
console.log(JSON.stringify(await response.json(), null, 2)) require "json"
require "net/http"
query = <<~'GRAPHQL'
mutation ImportCreatorsDefault($creators: [CreatorImportInput!]!, $operationId: ID) {
importCreators(creators: $creators, operationId: $operationId) {
operationId
acceptedCount
skipped {
index
provider
handle
reason
}
userErrors {
field
message
}
}
}
GRAPHQL
response = Net::HTTP.post(
URI("https://app.archive.com/api/v2"),
{ query: query, variables: { "creators" => [{ "handles" => [{ "provider" => "INSTAGRAM", "handle" => "@maya.greenhouse" }, { "provider" => "TIKTOK", "handle" => "mayagreenhouse" }], "labels" => ["Q4 seeding"] }, { "handles" => [{ "provider" => "YOUTUBE", "handle" => "fernandfrondstudio" }] }, { "handles" => [{ "provider" => "INSTAGRAM", "handle" => "not a handle!" }] }], "operationId" => "3f1c9a52-7d4e-4b8a-9c21-6e5f0a7b8d13" } }.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 ImportCreatorsDefault($creators: [CreatorImportInput!]!, $operationId: ID) {
importCreators(creators: $creators, operationId: $operationId) {
operationId
acceptedCount
skipped {
index
provider
handle
reason
}
userErrors {
field
message
}
}
}
"""
response = requests.post(
"https://app.archive.com/api/v2",
json={"query": query, "variables": {"creators": [{"handles": [{"provider": "INSTAGRAM", "handle": "@maya.greenhouse"}, {"provider": "TIKTOK", "handle": "mayagreenhouse"}], "labels": ["Q4 seeding"]}, {"handles": [{"provider": "YOUTUBE", "handle": "fernandfrondstudio"}]}, {"handles": [{"provider": "INSTAGRAM", "handle": "not a handle!"}]}], "operationId": "3f1c9a52-7d4e-4b8a-9c21-6e5f0a7b8d13"}},
headers={
"Authorization": "Bearer docs_demo_token_0000000000000000000000",
"WORKSPACE-ID": "6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a",
},
)
print(json.dumps(response.json(), indent=2)) {
"data": {
"importCreators": {
"operationId": "3f1c9a52-7d4e-4b8a-9c21-6e5f0a7b8d13",
"acceptedCount": 2,
"skipped": [
{
"index": 2,
"provider": "INSTAGRAM",
"handle": "not a handle!",
"reason": "INVALID_HANDLE"
}
],
"userErrors": []
}
}
} Retry with the same operationId
Generate a version 4 UUID for each import and send it as operationId. If the request times
out or the connection drops, send it again with the same operationId:
- If the first request got through, the retry is refused with a
userErrorsentry on["operationId"]and the first import keeps running. The HTTP response is still200; there is no HTTP409. Poll the operation with the ID you generated. - If it did not, the retry starts the import.
Never resubmit without the id while you wait for an import. A request without an operationId, or
with a new one, starts a second import of the same creators.
A request can also be refused because another import is updating the workspace’s labels at that
moment. It gets a userErrors entry on ["base"] asking you to retry, and nothing is imported.
Retrying it with the same operationId is safe.
Skipped creators
A creator that cannot be imported is reported in skipped instead of failing the whole request.
Each entry is { index, provider, handle, reason }, where index is the creator’s position in
creators:
INVALID_HANDLE— a handle is not a valid handle or profile URL on its platform.NO_HANDLES— the creator had no handles left to import.UNKNOWN_PROVIDER— a handle named a platform the import does not support.
acceptedCount counts the creators that were accepted. Fix a skipped creator’s handle before you
send it again; resending it unchanged skips it again. When every creator is skipped, no import is
started and operationId is null.
Follow the import
An accepted request does not mean the import has finished. Follow the operation in one of two ways:
- Poll. Query
operation(id:)with theoperationIduntilstatusisCOMPLETED,PARTIALorFAILED. A creator import reportsoperationTypeoneschema_creator_importandimportSourcePUBLIC_API. - Subscribe. Point a webhook subscription at the
operation.completedevent and wait for it. See operation completion events.
Then page through operationRecords(operationId:) for the outcome of
each creator. On a creator-import record, itemId is the row key within the import (import_0,
import_1, and so on), not an item id, and the record carries:
handles— the handles you sent for that creator;creatorId— the creator aSUCCEEDEDrow created or updated, an input tocreator(id:);globalCreatorId— for aSUCCEEDEDrow, the global, person-level id of the creator behind the row’s main handle. It spans workspaces and stays the same when the same person is imported into another workspace. It is not an input tocreator(id:); usecreatorIdfor that;errorCodeanderrorMessage— the reason aFAILEDrow failed. The message is suitable for display to a user.
The deprecated succeededItemIds, failedItemIds and pendingItemIds lists on Operation are
always empty for a creator import; use operationRecords instead.
Result CSVs
For a PARTIAL or FAILED import, Archive generates result CSVs shortly after the operation finishes:
failedRecordsCsvUrl— the rows that failed, each with its error (PARTIALandFAILED).successfulRecordsCsvUrl— the rows that were imported (PARTIALonly).
A COMPLETED import has no CSVs, and both fields are null until the files are written. Each link
is valid for 1 hour; query the operation again for a fresh one.
Pricing
The import itself uses no credits. Magic fields that run on the imported creators use credits exactly as they do for creators imported in the app. The request costs the same weighted rate-limit points as any other mutation; see rate limits.
Attribution
The import is recorded as made by a person in your workspace, as imports made in the app are:
- 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"].
Regenerating an API token in the app makes you its issuer.
From an MCP client
The importCreators MCP tool takes the same creators and
operationId arguments and returns the same payload. Poll the import with getOperation, read its
rows with getOperationRecords, and pass a row’s creatorId to getCreator.