How Account Linking Works Between Grok Web, Build, and Console in grok2api

Grok Web, Build, and Console accounts are linked through two many-to-one association tables—account_provider_links for Web-to-Build relationships and web_console_account_links for Web-to-Console relationships—enabling cross-provider operations with PostgreSQL advisory locks ensuring data consistency.

The grok2api repository implements a multi-provider identity architecture where each service maintains distinct account records. Understanding how account linking works between Grok Web, Build, and Console is essential for administrators managing unified user identities and performing cross-platform operations.

Data Model and Database Schema

Provider-Specific Account Records

Each service maintains its own provider account records identified by the provider fields grok_web, grok_build, and grok_console. Rather than merging these into a single table, the system preserves separate records for each platform while establishing formal relationships through dedicated link tables.

Association Tables

The relational persistence layer defines two critical junction tables in backend/internal/infra/persistence/relational/models.go:

  • account_provider_links: Establishes Web-to-Build relationships with columns web_account_id and build_account_id (lines 92-100)
  • web_console_account_links: Establishes Web-to-Console relationships with columns web_account_id and console_account_id (lines 102-108)

Both tables enforce unique constraints preventing a Web account from linking to multiple Build or Console accounts, and vice versa. The database schema includes check constraints (chk_account_provider_links_distinct) that maintain one-to-one cardinality between linked providers.

The Account Linking Flow

The linking process follows a layered architecture ensuring transactional safety:

  1. API Endpoint: The admin UI issues POST or PUT requests to /api/admin/v1/accounts/:id/link, handled in backend/internal/transport/http/account/handler.go

  2. Service Layer: The handler delegates to AccountService.LinkWebToBuild (or the Console equivalent) for business logic validation

  3. Repository Implementation: LinkWebToBuild in backend/internal/infra/persistence/relational/account_repository.go executes the insert within a database transaction (lines 941-950)

  4. Concurrency Control: A PostgreSQL advisory lock (accountLinkLock) serializes concurrent mutation attempts, preventing race conditions that could create duplicate links (tested in postgres_integration_test.go)

  5. Referential Integrity: Foreign-key constraints with ON DELETE CASCADE automatically remove link table rows when the parent Web account is deleted, maintaining database consistency without manual cleanup

Retrieving Linked Accounts

When fetching account details via GET /api/admin/v1/accounts/:id, the system populates a linkedAccounts slice containing relationship metadata. The response structure defined in backend/internal/transport/http/account/handler.go (lines 310-313) includes:

  • linkedAccountId: The UUID of the paired account
  • linkedProvider: The provider type (grok_build or grok_console)
  • linkedAccounts: An array of objects containing id, provider, name, email, and userId

This allows admin interfaces to display complete relationship graphs without requiring additional API calls.

Cascading Deletion of Linked Accounts

Account deletion supports optional cascading to linked peers through the linkedDeleteTargets parameter. When a request includes specific providers (e.g., ["grok_build","grok_console"]), the system:

  1. Parses targets using parseLinkedDeleteTargets in handler.go (line 1312)
  2. Resolves linked IDs via ResolveLinkedDeleteIDs in backend/internal/repository/account.go
  3. Expands the deletion scope to include one-hop linked accounts
  4. Reports both deleted (root) and linkedDeleted (peer) counts in the response (lines 663-669)

This atomic operation ensures that removing a Grok Web account can simultaneously clean up associated Build and Console identities without orphaning records.

Implementation Examples

Linking a Web Account to a Build Account

The repository method handles the actual database insertion with transactional safety:

// ctx – request context
// webID  – ID of the Grok Web account
// buildID – ID of the Grok Build account to link
err := accountsRepo.LinkWebToBuild(ctx, webID, buildID)
if err != nil {
    // handle ErrConflict (already linked) or other DB errors
}

This corresponds to the implementation in backend/internal/infra/persistence/relational/account_repository.go where the function inserts a row into account_provider_links while respecting the advisory lock mechanism.

Querying Linked Accounts via HTTP

curl -H "Authorization: Bearer <admin-token>" \
     https://api.grok.com/api/admin/v1/accounts/12345

The response includes relationship metadata:

{
  "id": 12345,
  "provider": "grok_web",
  "linkedAccountId": 67890,
  "linkedProvider": "grok_build",
  "linkedAccounts": [
    {
      "id": 67890,
      "provider": "grok_build",
      "name": "my-build-acct",
      "email": "build@example.com",
      "userId": "build-user"
    }
  ]
}

Deleting with Cascading Peers

POST /api/admin/v1/accounts/12345/delete
{
  "provider": "grok_web",
  "linkedDeleteTargets": ["grok_build"]
}

The handler processes the linkedDeleteTargets array to determine which associated providers should be removed alongside the primary account.

Summary

  • Dual Table Architecture: Account linking between Grok Web, Build, and Console relies on separate account_provider_links and web_console_account_links tables to maintain strict relationship cardinality
  • Transactional Safety: All linking operations use PostgreSQL advisory locks and database transactions to prevent duplicate entries during concurrent access
  • API Integration: The HTTP layer exposes linking capabilities through /api/admin/v1/accounts/:id/link with comprehensive linked account metadata in GET responses
  • Cascading Operations: Deletion requests can propagate to linked accounts via linkedDeleteTargets, with the service layer handling ID resolution and atomic cleanup
  • Source Locations: Core implementations reside in backend/internal/infra/persistence/relational/account_repository.go for data access and backend/internal/transport/http/account/handler.go for HTTP transport concerns

Frequently Asked Questions

What database tables store the account linking relationships?

The system uses two tables defined in backend/internal/infra/persistence/relational/models.go: account_provider_links connects Grok Web to Grok Build accounts, while web_console_account_links connects Grok Web to Grok Console accounts. Both tables use composite primary keys and unique constraints to enforce one-to-one relationships between providers.

The implementation uses PostgreSQL advisory locks (accountLinkLock) to serialize concurrent linking attempts, combined with database-level unique constraints on the link tables. This ensures that two simultaneous requests cannot create conflicting associations between the same Web and Build (or Console) accounts.

No. The database schema enforces unique constraints that prevent many-to-many relationships. Each Build account can link to only one Web account, and each Web account can link to only one Build account. This one-to-one cardinality is maintained through the chk_account_provider_links_distinct check constraint.

What happens to linked accounts when I delete a Grok Web account?

By default, only the Web account is deleted. However, if you specify linkedDeleteTargets in the deletion request (e.g., ["grok_build","grok_console"]), the system resolves the linked IDs and performs an atomic deletion of all specified accounts. Additionally, foreign-key constraints with ON DELETE CASCADE automatically clean up orphaned entries in the link tables regardless of the cascading deletion option.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →