# How to Migrate Accounts from the Legacy Python Version of Grok to Grok2API

> Migrate accounts from legacy Python Grok to Grok2API by exporting SSO tokens to a file and importing via the HTTP endpoint. Deduplicate entries and sync metadata automatically.

- Repository: [Chenyme/grok2api](https://github.com/chenyme/grok2api)
- Tags: migration-guide
- Published: 2026-08-09

---

**You can migrate accounts from the legacy Python Grok client to Grok2API by exporting your SSO tokens to a plain-text file and importing them via the dedicated HTTP endpoint, which automatically deduplicates entries and synchronizes account metadata with upstream services.**

The legacy Python implementation of Grok stored authentication tokens in isolated files that are incompatible with Grok2API's unified account database. When you migrate accounts from the legacy Python version of Grok, Grok2API's import pipeline parses these legacy formats, normalizes them to the current schema, and triggers background synchronization to pull quota and model capabilities from the Grok Web service.

## Prerequisites: Exporting Legacy Credentials

Before initiating the migration, you must extract your existing credentials from the legacy Python client. The legacy system typically stores Web SSO tokens in JSON or plain-text formats that Grok2API recognizes.

Export your tokens to a `.txt` file containing one SSO token per line. You can optionally prefix each token with an account name for easier identification:

```bash

# Example export command from the legacy Python client

grok-cli export-web-tokens --output tokens.txt

```

The resulting file should follow this structure:

```

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....

```

## Import Methods

Grok2API supports three primary methods for importing your exported credentials: HTTP API, Admin Console UI, and direct Go service invocation.

### Import via HTTP API Endpoint

The `POST /accounts/web/import` endpoint receives your token file and initiates the import pipeline. In [`backend/internal/transport/http/account/handler.go`](https://github.com/chenyme/grok2api/blob/main/backend/internal/transport/http/account/handler.go) (lines 42-44), the route is registered as:

```go
router.POST("/accounts/web/import", h.importWebAuth)

```

The `importWebAuth` handler (line 45) forwards the uploaded file to the application layer. Use `curl` to import your tokens:

```bash
curl -X POST http://localhost:8000/api/admin/v1/accounts/web/import \
  -H "Authorization: Bearer <admin-jwt>" \
  -F "file=@tokens.txt"

```

### Import via Admin Console UI

For operators preferring a graphical interface, the Admin Console provides a web-based upload workflow:

1. Navigate to `http://localhost:8000` and authenticate as an administrator.
2. Select **Accounts → Import** from the navigation menu.
3. Click **Upload file** and select your exported [`tokens.txt`](https://github.com/chenyme/grok2api/blob/main/tokens.txt).
4. Submit the form to trigger the import.

The UI displays a real-time progress bar driven by the `BatchProgressObserver`, which tracks the `ImportWebCredentialDocumentsWithProgress` execution.

### Programmatic Import Using Go Service

For automation scenarios, you can invoke the import service directly from Go code. The `ImportWebCredentialDocumentsWithProgress` function in [`backend/internal/application/account/service.go`](https://github.com/chenyme/grok2api/blob/main/backend/internal/application/account/service.go) (lines 66-70) handles the core logic:

```go
package main

import (
    "context"
    "io/ioutil"
    "log"

    "github.com/chenyme/grok2api/backend/internal/application/account"
)

func main() {
    // Load the legacy token file
    data, err := ioutil.ReadFile("tokens.txt")
    if err != nil {
        log.Fatalf("read file: %v", err)
    }

    // Obtain service instance from your DI container
    var svc *account.Service // initialized via your application context

    // Execute import
    result, err := svc.ImportWebCredentials(context.Background(), data)
    if err != nil {
        log.Fatalf("import failed: %v", err)
    }

    log.Printf("Import completed – created: %d, updated: %d", result.Created, result.Updated)
}

```

## How the Import Pipeline Works

The migration workflow consists of three architectural layers that ensure data integrity and idempotency.

### HTTP Handler Layer

The transport layer in [`handler.go`](https://github.com/chenyme/grok2api/blob/main/handler.go) receives multipart form data containing the token file. The `importWebAuth` method validates the request and calls `importFile` with the provider type `accountdomain.ProviderWeb`, routing the data to the appropriate codec.

### Account Service Processing

The `importCredentialDocumentsWithProgress` function (lines 99-124 in [`service.go`](https://github.com/chenyme/grok2api/blob/main/service.go)) implements the core parsing logic:

- **Credential Codec**: The service retrieves the Web-specific `CredentialCodec` from the provider registry to parse each line of the input file.
- **Seed Generation**: Valid tokens are converted to `provider.CredentialSeed` structs.
- **Batch Persistence**: The `persistImportedSeeds` function (lines 55-58) writes accounts to the database in batches and calls `reconcileProviderLinksBestEffort` to link new Web accounts with existing Build/Console accounts.

### Idempotency and Deduplication

The pipeline is fully idempotent. A `seen` map within `importCredentialDocumentsWithProgress` tracks tokens during processing, silently skipping duplicates. This ensures you can safely re-run the migration if your token file contains overlapping entries or if you need to incrementally add new accounts.

## Post-Migration Synchronization

After successful import, Grok2API automatically initiates background synchronization. The `reconcileProviderLinksBestEffort` function queues quota retrieval and model capability updates from the upstream Grok Web service. You do not need to manually refresh account metadata; the system pulls the latest configuration within seconds of import completion.

## Summary

- **Export legacy tokens** from the Python client to a plain-text file with one token per line.
- **Use the HTTP endpoint** `POST /accounts/web/import` or the Admin Console UI to upload credentials.
- **The import pipeline** in [`backend/internal/application/account/service.go`](https://github.com/chenyme/grok2api/blob/main/backend/internal/application/account/service.go) handles parsing via `ImportWebCredentialDocumentsWithProgress`.
- **Deduplication is automatic**; the `seen` map ensures idempotent imports.
- **Background sync** automatically updates quota and model metadata after import.

## Frequently Asked Questions

### What file format does Grok2API expect for legacy token imports?

Grok2API accepts plain-text files (`.txt`) containing one SSO token per line. The `CredentialCodec` for Web credentials in the provider registry handles parsing, and the system tolerates optional account name prefixes while normalizing entries to the current account schema.

### Is the migration process safe to run multiple times?

Yes. The import pipeline is idempotent. The `importCredentialDocumentsWithProgress` function maintains a `seen` map that deduplicates tokens during processing, so duplicate entries in your file or re-importing the same file will not create duplicate accounts.

### Which source files control the import logic?

The HTTP route is defined in [`backend/internal/transport/http/account/handler.go`](https://github.com/chenyme/grok2api/blob/main/backend/internal/transport/http/account/handler.go) (lines 42-45), while the business logic resides in [`backend/internal/application/account/service.go`](https://github.com/chenyme/grok2api/blob/main/backend/internal/application/account/service.go) (lines 66-70 and 99-124). The `importWebAuth` handler forwards requests to `ImportWebCredentialDocumentsWithProgress`, which coordinates parsing and persistence.

### Does Grok2API preserve the old `clientKeyID` field from legacy exports?

The migration pipeline ignores the legacy `clientKeyID` field for backward compatibility but does not store it in the new schema. This follows the repository's philosophy of tolerating older token structures while normalizing them to the current `account` model, as noted in the project's README.