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

# Compare MEV Timing

> Compare MEV timing behavior across the network, staking entities, sub-entities, and your validator sets

## Overview

MEV timing is most useful as a comparison. A single late proposal may be noise, but a sustained difference from the network can reveal deliberate timing behavior, relay latency, or a configuration change.

This guide shows two complementary workflows:

1. Compare an entity's daily timing trend with the network over the same UTC dates.
2. Profile your own validator set with a rolling 180-day timing label and histogram.

<Note>
  **API Endpoints:** This guide uses [`/api/v2/ethereum/mev-timing`](/api-reference/ethereum/mev-timing), [`/api/v2/ethereum/entity/mev-timing`](/api-reference/ethereum/entity/mev-timing), and [`/api/v2/ethereum/validators/mev-timing-aggregate`](/api-reference/ethereum/validators/mev-timing-aggregate).
</Note>

<Info>
  The entity daily endpoint requires a Scale or Enterprise plan. You can query the validator aggregate with explicit validator identifiers or your dashboard on free plans.
</Info>

***

## Comparison Strategy

Use the daily network and entity series for like-for-like date comparisons, and use the validator aggregate when its rolling 180-day window matches your question. Report sample size and relay coverage with every result; inspect the distribution when a median could hide mixed behavior or long tails.

API references: [Network timing](/api-reference/ethereum/mev-timing), [entity timing](/api-reference/ethereum/entity/mev-timing), and [validator aggregate](/api-reference/ethereum/validators/mev-timing-aggregate).

<Warning>
  Do not average daily medians to create a period median. Medians are not additive. Aggregate the histogram for an approximate period distribution, or use the validator aggregate when its rolling 180-day window matches your question.
</Warning>

<Warning>
  The timing bands describe pre-Glamsterdam slots and may change when Glamsterdam restructures slot production with ePBS. Relay timing is also offchain observation data, not independently verifiable onchain. Compare like-for-like protocol eras, report relay-data coverage, and treat missing observations as an uncertainty rather than evidence that no timing game occurred.
</Warning>

***

## Compare an Entity with the Network

Request both daily series with the same timestamp range. The example uses June 2026 and a page size large enough to return all 30 days in one request.

### Network Request

```bash theme={null}
curl --request POST \
  --url https://beaconcha.in/api/v2/ethereum/mev-timing \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "chain": "mainnet",
  "range": {
    "timestamp": {
      "start": 1780272000,
      "end": 1782863999
    }
  },
  "min_slots_per_day": 100,
  "page_size": 100
}
'
```

### Entity Request

```bash theme={null}
curl --request POST \
  --url https://beaconcha.in/api/v2/ethereum/entity/mev-timing \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "chain": "mainnet",
  "entity": "Lido",
  "range": {
    "timestamp": {
      "start": 1780272000,
      "end": 1782863999
    }
  },
  "page_size": 100
}
'
```

Entity and sub-entity names are case-sensitive. Get the canonical spelling from the [`entities`](/api-reference/ethereum/entities/overview) and [`entity/sub-entities`](/api-reference/ethereum/entity/sub-entities) endpoints. Add `"sub_entity": "<NAME>"` to scope the second request.

### Calculate Daily Deltas with Python

```python theme={null}
import os

import requests

BASE_URL = "https://beaconcha.in/api/v2/ethereum"
HEADERS = {
    "Authorization": f"Bearer {os.environ['BEACONCHAIN_API_KEY']}",
    "Content-Type": "application/json",
}
RANGE = {
    "timestamp": {
        "start": 1780272000,
        "end": 1782863999,
    }
}


def post(path, payload):
    response = requests.post(
        f"{BASE_URL}/{path}", headers=HEADERS, json=payload, timeout=30
    )
    response.raise_for_status()
    return response.json()["data"]


def timing_game_share(row):
    denominator = row["slots_with_relay_winners"]
    if denominator == 0:
        return None
    breakdown = row["timing_breakdown"]
    delayed = breakdown["timing_games"] + breakdown["aggressive_tg"]
    return delayed / denominator


network_rows = post(
    "mev-timing",
    {
        "chain": "mainnet",
        "range": RANGE,
        "min_slots_per_day": 100,
        "page_size": 100,
    },
)
entity_rows = post(
    "entity/mev-timing",
    {
        "chain": "mainnet",
        "entity": "Lido",
        "range": RANGE,
        "page_size": 100,
    },
)

network_by_date = {row["date"]: row for row in network_rows}

for entity_row in sorted(entity_rows, key=lambda row: row["date"]):
    network_row = network_by_date.get(entity_row["date"])
    if network_row is None:
        continue

    entity_share = timing_game_share(entity_row)
    network_share = timing_game_share(network_row)
    if (
        entity_share is None
        or network_share is None
        or entity_row["median_slot_offset_ms"] is None
        or network_row["median_slot_offset_ms"] is None
    ):
        continue

    median_delta = (
        entity_row["median_slot_offset_ms"]
        - network_row["median_slot_offset_ms"]
    )
    share_delta = entity_share - network_share

    print(
        entity_row["date"],
        f"median delta: {median_delta:+.0f} ms",
        f"timing-game share delta: {share_delta:+.2%}",
        f"entity sample: {entity_row['slots_with_relay_winners']}",
    )
```

### Calculate Daily Deltas with JavaScript

This example uses the built-in `fetch` available in Node.js 18 and later.

```javascript theme={null}
const baseUrl = 'https://beaconcha.in/api/v2/ethereum'
const headers = {
  Authorization: `Bearer ${process.env.BEACONCHAIN_API_KEY}`,
  'Content-Type': 'application/json',
}
const range = {
  timestamp: {
    start: 1780272000,
    end: 1782863999,
  },
}

async function post(path, payload) {
  const response = await fetch(`${baseUrl}/${path}`, {
    method: 'POST',
    headers,
    body: JSON.stringify(payload),
  })

  if (!response.ok) {
    throw new Error(`${response.status} ${await response.text()}`)
  }

  return (await response.json()).data
}

function timingGameShare(row) {
  const denominator = row.slots_with_relay_winners
  if (denominator === 0) return null

  const delayed = row.timing_breakdown.timing_games
    + row.timing_breakdown.aggressive_tg
  return delayed / denominator
}

const [networkRows, entityRows] = await Promise.all([
  post('mev-timing', {
    chain: 'mainnet',
    range,
    min_slots_per_day: 100,
    page_size: 100,
  }),
  post('entity/mev-timing', {
    chain: 'mainnet',
    entity: 'Lido',
    range,
    page_size: 100,
  }),
])

const networkByDate = new Map(networkRows.map(row => [row.date, row]))

for (const entityRow of entityRows.sort((a, b) => a.date.localeCompare(b.date))) {
  const networkRow = networkByDate.get(entityRow.date)
  if (!networkRow) continue

  const entityShare = timingGameShare(entityRow)
  const networkShare = timingGameShare(networkRow)
  if (
    entityShare === null
    || networkShare === null
    || entityRow.median_slot_offset_ms === null
    || networkRow.median_slot_offset_ms === null
  ) continue

  const medianDelta = entityRow.median_slot_offset_ms
    - networkRow.median_slot_offset_ms
  const shareDelta = entityShare - networkShare

  console.log({
    date: entityRow.date,
    median_delta_ms: medianDelta,
    timing_game_share_delta: shareDelta,
    entity_sample: entityRow.slots_with_relay_winners,
  })
}
```

<Note>
  Daily rows can have `median_slot_offset_ms: null` when there are no identified relay winners. The examples skip those rows before calculating deltas.
</Note>

***

## Profile Your Validator Set

Use the validator aggregate to summarize a validator list, dashboard, dashboard group, withdrawal credential, deposit address, entity, or sub-entity. Currently, the only supported evaluation window is `180d`.

```bash theme={null}
curl --request POST \
  --url https://beaconcha.in/api/v2/ethereum/validators/mev-timing-aggregate \
  --header 'Authorization: Bearer <YOUR_API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "chain": "mainnet",
  "validator": {
    "dashboard_id": 123,
    "group_id": 7
  },
  "evaluation_window": "180d"
}
'
```

The following shortened response illustrates the comparison workflow. See the [validator timing aggregate reference](/api-reference/ethereum/validators/mev-timing-aggregate).

```json theme={null}
{
  "data": {
    "timing_label": {
      "status": "timing_games",
      "median_slot_offset_ms": 1376,
      "total_count": 284
    },
    "histogram": [
      {
        "slot_offset_ms_min": 1200,
        "slot_offset_ms_max": 1400,
        "count": 31,
        "max_value": "194000000000000000"
      }
    ],
    "evaluation_window": "180d"
  }
}
```

Use the complete distribution to check whether the median reflects a tight cluster or hides a long aggressive tail.

<Tip>
  Create dashboard groups for infrastructure cohorts such as client, region, node, or relay configuration. Query each `group_id` separately to isolate where a timing shift originates.
</Tip>

***

## Comparison Checklist

<CardGroup cols={2}>
  <Card title="Align UTC Dates" icon="calendar">
    Compare daily rows only when their `date` values match. Do not compare a partial day with a complete day.
  </Card>

  <Card title="Report Sample Size" icon="hashtag">
    Always show `slots_with_relay_winners` or `total_count` next to a median or percentage.
  </Card>

  <Card title="Inspect the Tail" icon="chart-column">
    A healthy median can coexist with aggressive outliers. Review histogram counts beyond 2,600 ms.
  </Card>

  <Card title="Allow for Reprocessing" icon="rotate">
    Exclude the newest three daily buckets from finalized reports because they may still change.
  </Card>
</CardGroup>

***

## Related Resources

* [MEV Analysis Introduction](/use-cases/mev-analysis-introduction) — Endpoint selection, timing bands, and data freshness
* [Investigate a Block](/use-cases/investigate-mev-block) — Drill from an aggregate outlier into one proposal
* [MEV Timing Games](/mev-timing-games/introduction) — Why operators delay and what the thresholds mean
* [Dashboard as Private Sets](/use-cases/rewards-dashboard-private-sets) — Organize validators into reusable groups
