> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ezforge.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Business Verification

> Multi-level trust system for Ready businesses — earn verification badges that signal credibility to AI agents and customers

## Overview

Ready uses a four-tier verification system to establish trust for businesses in the directory and in MCP server metadata. Higher verification levels improve your directory ranking and signal credibility to AI agents that interact with your business.

<Info>
  Business Verified and Premium Verified tiers are in Beta. Business Verified auto-checks will
  complete when Google Business Profile (GBP) API integration launches (EP-R07 post-launch).
  Phone Verified is fully operational.
</Info>

## Verification tiers

Tiers are strictly ordered — you cannot skip a level.

| Tier                | Display label     | How it is awarded                                                                                                       |
| ------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `unverified`        | Unverified        | Default for all new businesses                                                                                          |
| `phone_verified`    | Phone Verified    | User-initiated: complete SMS verification from your Ready settings page — tier is awarded automatically on confirmation |
| `business_verified` | Business Verified | Auto-check: account ≥ 30 days old + phone verified (GBP cross-reference in Beta — logs but is not blocking)             |
| `premium_verified`  | Premium Verified  | Manual review by ezForge team (post-launch queue, no automated promotion during Beta)                                   |

## Earning verification

### Phone Verified

Complete phone verification from your Ready settings page. Ready sends an SMS code to your registered business phone number. When the code is confirmed, your account is automatically upgraded to `phone_verified`.

### Business Verified

The auto-check runs after phone verification and evaluates:

1. **Account age** — your business account must be at least 30 days old
2. **Phone verification** — must already be at `phone_verified` or higher
3. **GBP cross-reference** — Google Business Profile match (Beta stub; currently always `false` but non-blocking)

When criteria 1 and 2 are met, the system upgrades your tier to `business_verified` automatically.

### Premium Verified

Contact ezForge support or use the in-dashboard option to apply. Your business is placed in a manual review queue (`premium_review_queued_at` is set). A team member reviews and promotes the tier. No automated promotion occurs.

## Verification badge

The verification badge appears on your Ready settings page at `/ready/settings`. It shows:

* **Tier label** — color-coded badge (gray → blue → green → purple)
* **Description** — what the current tier means
* **Next step** — guidance on how to advance to the next tier (hidden at Premium Verified)
* **Stale data warning** — if your business hours have not been updated in 90 days, a yellow warning banner is shown

### Stale data detection

Ready monitors how recently your business hours were updated. If no update is recorded in the last 90 days, a stale flag is set on your verification record. Stale businesses receive a warning in the dashboard prompting an update. Updating your hours clears the flag automatically.

## `VerificationLevel` type

The `VerificationLevel` type is the public-facing string representation of a business's verification tier. It is returned in directory API responses and MCP server metadata.

```typescript theme={null}
type VerificationLevel =
  | 'unverified'
  | 'phone_verified'
  | 'business_verified'
  | 'premium_verified';
```

**Mapping from internal tier to `VerificationLevel`:**

| Internal `VerificationTier` | `VerificationLevel` |
| --------------------------- | ------------------- |
| `unverified`                | `unverified`        |
| `phone`                     | `phone_verified`    |
| `business`                  | `business_verified` |
| `premium`                   | `premium_verified`  |

When no `business_verification_levels` row exists for a business (pre-migration records), the system falls back to checking the `phone_verifications` table. A confirmed phone record maps to `phone_verified`; no record maps to `unverified`.

## `business_verification_levels` schema

One row per business. Created on first verification activity; `upsertBusinessVerificationLevel` inserts a default `unverified` row if none exists.

| Column                     | Type                     | Description                                                                  |
| -------------------------- | ------------------------ | ---------------------------------------------------------------------------- |
| `business_id`              | `varchar(26)`            | Primary key — foreign key to `businesses.id` (cascade delete)                |
| `tier`                     | `verification_tier` enum | Current tier: `unverified`, `phone`, `business`, `premium`                   |
| `achieved_at`              | `timestamp`              | When the current tier was last achieved (null for `unverified`)              |
| `gbp_verified`             | `boolean`                | Whether a Google Business Profile match was confirmed (default `false`)      |
| `gbp_place_id`             | `varchar(255)`           | Matched GBP place ID, for audit trail (null until GBP integration)           |
| `stale_flagged_at`         | `timestamp`              | Set when business hours are more than 90 days stale; cleared on hours update |
| `premium_review_queued_at` | `timestamp`              | Set when business is queued for Premium Verified manual review               |
| `created_at`               | `timestamp`              | Row creation time                                                            |
| `updated_at`               | `timestamp`              | Last update time                                                             |

**Indexes:** `business_verification_levels_tier_idx` on `tier` (used by directory filter queries).

**Hub listing sync:** On every tier upgrade, the integer value of the new tier (1–4) is mirrored into `hub_listings.verification_tier` for Typesense rank-score computation. This write is best-effort — hub listing failure does not block the tier upgrade.

## `getBusinessTrustSignals`

Returns a concise set of trust signals for a business, suitable for inclusion in MCP server metadata.

**Parameters:**

| Parameter    | Type       | Description               |
| ------------ | ---------- | ------------------------- |
| `database`   | `Database` | Drizzle database instance |
| `businessId` | `string`   | Business ULID             |

**Returns:**

```typescript theme={null}
{
  verificationTier: VerificationTier;  // 'unverified' | 'phone' | 'business' | 'premium'
  accountAgeDays: number;              // days since the business was created
  staleFlagged: boolean;               // true if stale_flagged_at is set
}
```

**Throws:** `NotFoundError` when the business does not exist.

`getBusinessTrustSignals` and `getMcpServerMetadata` are independent utilities — neither calls the other. They surface overlapping signals (`accountAgeDays`) to different consumers via separate SQL queries. The differences in their output shapes are intentional:

* `staleFlagged` is returned by this function for stale-data detection (see [Stale data detection](#stale-data-detection)) but is not part of the MCP metadata trust signals.
* `profileCompleteness`, `lastUpdated`, and `interactionCount` appear in the MCP trust signals shape but are **not** returned by this function — `getMcpServerMetadata` computes them directly from its own SQL join (`profileCompleteness` from the business profile record, `lastUpdated` from the listing row, `interactionCount` from an analytics count query).

See [Directory API](/api-reference/directory#mcp-server-metadata) for the full MCP response shape.

## Directory API integration

Verification level is included in all directory responses (see [Directory API](/api-reference/directory)):

* **List directory** — `verificationLevel` field on each listing item
* **Get listing** — `verificationLevel` field on the detail response
* **MCP server metadata** — `business.verificationLevel` plus trust signals: `accountAgeDays`, `profileCompleteness`, `lastUpdated`, `interactionCount` (see [Directory API](/api-reference/directory#mcp-server-metadata) for full shape)

You can filter the directory by minimum verification level using the `min_verification` query parameter:

| Value               | Businesses returned                                     |
| ------------------- | ------------------------------------------------------- |
| `phone_verified`    | Phone Verified, Business Verified, and Premium Verified |
| `business_verified` | Business Verified and Premium Verified                  |
| `premium_verified`  | Premium Verified only                                   |

## Hub listing sync

When a business's verification tier is upgraded, the integer equivalent is written to `hub_listings.verification_tier`:

| Tier         | Integer |
| ------------ | ------- |
| `unverified` | 1       |
| `phone`      | 2       |
| `business`   | 3       |
| `premium`    | 4       |

The Hub uses this value in its Typesense rank-score computation. Higher-verified businesses rank higher in Hub search results.
