Skip to content

isVerified boolean conflates Twitter Blue, Business, and Government accounts #145

@RealADemin

Description

@RealADemin

ft's BookmarkAuthorSnapshot.isVerified boolean is true for any account with any kind of X checkmark — Twitter Blue subscribers, Business accounts, and Government accounts. Downstream consumers (CLI users, JSONL readers, GUIs) cannot distinguish these categorically different states from a single boolean.

The disambiguating fields exist on the same User object is_blue_verified is read from at src/graphql-bookmarks.ts:263. They're currently discarded.

Failing case (reproducible against live X API today, May 2026)

Three accounts representing the collapse:

Account Type is_blue_verified verification.verified_type ft's current isVerified
@usgraphics Business org true "Business" true
@itsjamiecho individual w/ Blue true absent true
@CENTCOM US Central Command (Government) true "Government" true

All three flatten to identical isVerified: true despite being categorically different account types. A consumer reading bookmark.author.isVerified cannot tell @CENTCOM (a US military command) apart from a random Blue subscriber.

Real-world scale: in a 5222-bookmark corpus, 3647 records (69.8%) carry author.isVerified === true, indistinguishably mixing all three account types.

Two observations from inspecting the live response

1. legacy.verified is gone from modern X responses. The current derivation at src/graphql-bookmarks.ts:263:

isVerified: Boolean(userResult?.is_blue_verified ?? userResult?.legacy?.verified),

The ?? userResult?.legacy?.verified fallback is dead code — legacy.verified is absent from user.legacy across all three test accounts (verified May 2026). In practice isVerified is effectively just is_blue_verified.

2. verification.verified (new sub-object) is false for all three accounts. Even inside the new verification object, the original "verified" semantic has been retired. verification.verified_type is the field X actually uses to mark organization checkmarks.

What X exposes today

All accessible from core.user_results.result.* in the same response ft already fetches per bookmark:

  • verification.verified_type"Business" | "Government" | absent. Drives the gold (Government) vs gray (Business) vs none checkmark distinction.
  • is_blue_verified — boolean, true for any checkmark of any kind. Worth preserving as a distinct field so downstream knows it now means "any check" rather than specifically "Blue subscriber."
  • profile_image_shape"Square" | "Circle". Direct organization indicator (Square for Business + Government, Circle for individuals). Simpler than parsing verified_type for the org/individual question.
  • affiliates_highlighted_label — affiliation badges. Empty object {} when absent; rich object when present. Example value from @itsjamiecho:
    {
      "label": {
        "badge": {"url": "https://pbs.twimg.com/profile_images/2038642627478704128/KTcKqWYg_bigger.jpg"},
        "description": "Cosmos",
        "url": {"url": "https://twitter.com/thecosmos", "urlType": "DeepLink"},
        "userLabelDisplayType": "Badge",
        "userLabelType": "BusinessLabel"
      }
    }
  • parody_commentary_fan_label — string. "None" when absent; other values when the account is in one of those categories. Attribution-critical: a tweet from a parody account currently looks identical to one from the real account in ft's data.

Fix

Layer 1 — type + parser (≈10 lines, no schema changes)

Extend BookmarkAuthorSnapshot at src/types.ts:21 and add five field reads inside the existing snapshot construction at src/graphql-bookmarks.ts:255-269:

// inside the existing snapshot construction block
verifiedType: userResult?.verification?.verified_type,
isBlueVerified: userResult?.is_blue_verified,
profileImageShape: userResult?.profile_image_shape,
affiliatesHighlightedLabel: userResult?.affiliates_highlighted_label,
parodyCommentaryFanLabel: userResult?.parody_commentary_fan_label,

JSONL serialization picks up the new fields automatically. No SQLite changes, no migration. This layer alone unblocks downstream consumers — JSONL is the source of truth per ft's design; SQLite is derived.

The existing isVerified field stays for backward compatibility. New fields are purely additive.

Optional cleanup while in this block: drop the ?? userResult?.legacy?.verified fallback at line 263 since it's dead code (the field is absent from modern responses).

Layer 2 — SQLite columns (optional follow-up)

If per-bookmark verification fields are wanted in SQLite too, add columns to the bookmarks table at src/bookmarks-db.ts:237 (currently has only author_handle, author_name, author_profile_image_url). Standard ALTER TABLE migration. Independent of Layer 1 and not required to resolve the misleading-data problem.

Why this is correctness, not feature

This isn't asking ft to capture data it doesn't already touch — is_blue_verified is already read at line 263. The issue is that the existing field's semantic became diluted as X added Blue / Business / Government / removed-legacy. ft never updated. Anyone using bookmark.author.isVerified today gets a misleading boolean by no fault of their own.

The fix is small (~10 lines) and additive. The minimum viable change preserves backward compatibility entirely while letting consumers opt into the disambiguation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions