# Importing creators

[`importCreators`](/api/v2/docs/mutations/import-creators) adds creators to your workspace from their social handles. It runs the same import engine as **Creators → Import from file** in the Archive app: each creator is matched to an existing creator or created from their handles, and the labels you send are applied to them, with any label the workspace does not have yet created on the way.

The mutation only accepts the batch. The import itself runs in the background, and you follow it through the operation it returns.

## Send a batch

Each request carries 1 to 250 creators. A creator is a list of 1 to 5 handles, each a `{ provider, handle }` pair, plus optional labels:

- `provider` is `INSTAGRAM`, `TIKTOK` or `YOUTUBE`.
- `handle` is the creator’s handle on that platform, with or without a leading `@`, or their profile URL there.
- `labels` holds 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.

A request that breaks any of these caps is refused 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"]`.

```graphql
mutation ImportCreatorsDefault($creators: [CreatorImportInput!]!, $operationId: ID) {
  importCreators(creators: $creators, operationId: $operationId) {
    operationId
    acceptedCount
    skipped {
      index
      provider
      handle
      reason
    }
    userErrors {
      field
      message
    }
  }
}
```

```json
{
  "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"
}
```

```http
Authorization: Bearer docs_demo_token_0000000000000000000000
WORKSPACE-ID: 6ccefa76-e8ba-5ab0-9c60-e48a9e235a4a
```

```json
{
  "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

Mint a version 4 UUID for each logical 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 `userErrors` entry on `["operationId"]` and the first import keeps running. The HTTP response is still `200`; there is no HTTP `409`. Poll the operation with the id you minted.
- 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.

> **Omit operationId only for fire-and-forget imports**
>
> Without an `operationId` the server mints one and returns it. That is fine when you never retry, but a retried request then has nothing to tie it to the first one.

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

When `importCreators` returns, nothing has been imported yet. Follow the operation in one of two ways:

- **Poll.** Query [`operation(id:)`](/api/v2/docs/queries/operation) with the `operationId` until `status` is `COMPLETED`, `PARTIAL` or `FAILED`. A creator import reports `operationType` `oneschema_creator_import` and `importSource` `PUBLIC_API`.
- **Subscribe.** Point a webhook subscription at the `operation.completed` event and wait for it. See [operation completion events](/api/v2/docs/guides/webhooks#operation-completion-events).

Then page through [`operationRecords(operationId:)`](/api/v2/docs/queries/operation-records) 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 a `SUCCEEDED` row created or updated, an input to [`creator(id:)`](/api/v2/docs/queries/creator);
- `errorCode` and `errorMessage` — why a `FAILED` row failed, the message in words fit to show a person.

The deprecated `succeededItemIds`, `failedItemIds` and `pendingItemIds` lists on `Operation` are always empty for a creator import; use `operationRecords` instead.

### Result CSVs

A `PARTIAL` or `FAILED` import also gets CSV files of its rows, written shortly after it finishes:

- `failedRecordsCsvUrl` — the rows that failed, each with its error (`PARTIAL` and `FAILED`).
- `successfulRecordsCsvUrl` — the rows that were imported (`PARTIAL` only).

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 charges no credits. Magic fields that run on the imported creators bill 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](/api/v2/docs/guides/rate-limits).

## Attribution

The import is recorded as made by a person in your workspace, as imports made in the app are:

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"]`. Regenerating an API token in the app makes you its issuer.

## From an MCP client

The [`importCreators`](/api/v2/docs/mcp/write-tools#import-creators) 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`.
