---
title: "Get Sync Metrics"
method: GET
path: "/v1/buckets/{bucket_id}/syncs/{sync_config_id}/metrics"
tags: ["Bucket Syncs"]
---

# Get Sync Metrics

`GET /v1/buckets/{bucket_id}/syncs/{sync_config_id}/metrics`

## Request

- Base URL: `https://api.mixpeek.com`
- URL: `https://api.mixpeek.com/v1/buckets/{bucket_id}/syncs/{sync_config_id}/metrics`
- Auth: HTTP bearer and API key in header `X-Namespace`

Operational metrics for a sync in one call.

Folds config totals + health/staleness + the DLQ failure breakdown (grouped
by normalized error reason) + the most-recent job's throughput into a single
response — so callers can see "is this sync healthy / is anything backlogged"
without stitching /jobs and /dlq or reading logs.

## Path parameters

- `bucket_id` string, required
- `sync_config_id` string, required

## Response `200`

Successful Response

- SyncMetricsResponse: Operational metrics for a sync configuration, in one call. Folds together what previously required stitching the config + /jobs + /dlq: lifetime totals, health/staleness, the DLQ failure breakdown (grouped by normalized error reason), and the most-recent job's throughput. Lets callers answer "is this sync healthy and is anything backlogged?" without circumventing the API with kubectl/log spelunking.
  - `sync_config_id` string, required
  - `status` string, required: Sync config status (e.g. ACTIVE).
  - `is_active` boolean, required
  - `paused` boolean
  - `total_files_discovered` integer
  - `total_files_synced` integer
  - `total_files_failed` integer
  - `batches_created` integer
  - `sync_run_counter` integer
  - `consecutive_failures` integer
  - `last_error` string, nullable
  - `last_sync_at` string, date-time, nullable
  - `next_sync_at` string, date-time, nullable
  - `seconds_since_last_sync` integer, nullable: Age of last successful sync; None if never synced.
  - `is_stale` boolean: True when a continuous sync has not progressed for more than TWO of its own polling intervals. Previously the bar was three intervals OR 600 seconds, whichever was larger, so a one-minute sync could miss ten consecutive runs and still read healthy while an hourly one got three. Read it beside intervals_since_last_sync and stale_after_seconds rather than on its own.
  - `intervals_since_last_sync` number, nullable: How overdue the sync is, measured in its OWN polling intervals: seconds_since_last_sync / polling_interval_seconds. 1.0 means one interval has elapsed, which is normal for a sync about to run. None when the sync has never run. Present so a caller can judge lateness without knowing where the threshold happens to sit.
  - `stale_after_seconds` integer, nullable: The threshold is_stale was evaluated against, in seconds, so the verdict can be checked rather than trusted. max(2 * polling_interval_seconds, 120).
  - `failure_rate` number: total_files_failed / total_files_discovered (0..1).
  - `healthy` boolean: Composite: active, not paused, no consecutive failures, not stale, running job not stalled. Because it folds in is_stale, a sync more than two intervals overdue reports healthy=false rather than true.
  - `dlq_total` integer: Objects stuck after all retries.
  - `dlq_by_error` object: DLQ counts grouped by normalized error reason (IDs stripped).
  - `running_job` boolean
  - `stalled` boolean: The running job has made no progress: no heartbeat, or nothing discovered or synced, for longer than max(stale_after_seconds, 300). A first run that has not finished yet has no last sync to be stale against, so this is how it reports a stall. Folds into healthy.
  - `stalled_reason` string, nullable: Why the running job counts as stalled, when it does.
  - `last_job` object, nullable: Summary of the most recent job: status, files_synced/failed, throughput_files_per_min, duration_seconds.

## Other responses

- `400`: Bad Request
- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not Found
- `422`: Unprocessable Entity
- `429`: Too Many Requests
- `500`: Internal Server Error
- `503`: Service Unavailable

## Changes

- **2026-10-21** `d4b5fad18960`: 2 info
  - added the optional property `stalled` to the response with the `200` status
  - added the optional property `stalled_reason` to the response with the `200` status
- **2026-10-09** `61a175e8eb9a`: 1 warning, 5 info
  - removed the optional property `detail` from the response with the `422` status
  - added the non-success response with the status `429`
  - added the non-success response with the status `503`
  - added the optional property `success` to the response with the `422` status
  - …2 more
- **2026-09-19** `4bbfcce2371b`: 2 info
  - added the optional property `intervals_since_last_sync` to the response with the `200` status
  - added the optional property `stale_after_seconds` to the response with the `200` status
- **2026-08-09** `5d4c905106b4`: 1 info
  - the endpoint scheme security `BearerAuth AND NamespaceHeader` was added to the API

[Operation history](https://skmtc.dev/mixpeek/apis/mixpeek-api/changes/v1/buckets/:bucket_id/syncs/:sync_config_id/metrics/get.md)

---

[API](https://skmtc.dev/mixpeek/apis/mixpeek-api.md) - [All operations](https://skmtc.dev/mixpeek/apis/mixpeek-api/llms.txt) - [OpenAPI document](https://skmtc.dev/mixpeek/apis/mixpeek-api/revisions/790bc16455f6?raw)
