MisarMisar Docs
MisarMailMisar.BlogMisarReachMisarPostMisar.DevMisarSEOMisar PlatformMisar SSO
API Reference

List Health

Monitor your mailing list quality, detect unengaged subscribers, score contacts, and run win-back workflows

List health tools help you maintain a clean, engaged mailing list. A healthy list improves deliverability, lowers spam complaint rates, and ensures your campaigns reach people who actually want to hear from you.

All list-health endpoints use session (cookie-based) authentication. They are dashboard-facing and do not accept API keys.

Sunset stages

List health works by identifying unengaged contacts — subscribers who have not opened or clicked within the configured window — and moving them through progressive sunset stages: warned → send frequency reducedremoved (suppressed). Each health run scans your contacts and advances or records these stages.

Run a list health check

POST/api/list-health/run

Runs a full health scan synchronously and returns the run's stats. Pass dryRun: true to compute what would change without writing anything.

Request body

dryRunbooleanbodydefault: false

When true, the scan reports counts without warning, reducing, or removing any contacts.

Response fields

successboolean

true when the run completed.

runIdstring

ID of the recorded run.

dryRunboolean

Echoes the requested mode.

statsobject

scanned, unengaged, warned, and removed counts for this run.

curl -X POST /api/list-health/run \
  -H "Content-Type: application/json" \
  -d '{ "dryRun": false }'
{
  "success": true,
  "runId": "9f1c2a7e-4b8d-4c2a-9e11-2f3a4b5c6d7e",
  "dryRun": false,
  "stats": {
    "scanned": 12400,
    "unengaged": 980,
    "warned": 640,
    "removed": 120
  }
}

Get health stats

GET/api/list-health/stats

Returns current list-health counters, the state of the win-back workflow, and recent runs.

Response fields

currentStatsobject

totalSubscribed, unengaged, sunsetWarned, sunsetReduced, and sunsetRemoved.

winBackWorkflowobject

exists, workflowId, status, totalEnrolled, and totalCompleted.

recentRunsArray<object>

Recent runs, each with id, run_type, started_at, completed_at, and a stats object (scanned, unengaged, warned, removed).

curl /api/list-health/stats
{
  "currentStats": {
    "totalSubscribed": 12400,
    "unengaged": 980,
    "sunsetWarned": 640,
    "sunsetReduced": 210,
    "sunsetRemoved": 120
  },
  "winBackWorkflow": {
    "exists": true,
    "workflowId": "7c1b2a3d-...",
    "status": "active",
    "totalEnrolled": 640,
    "totalCompleted": 180
  },
  "recentRuns": [
    {
      "id": "9f1c2a7e-...",
      "run_type": "list_health",
      "started_at": "2026-05-27T06:00:00Z",
      "completed_at": "2026-05-27T06:00:12Z",
      "stats": { "scanned": 12400, "unengaged": 980, "warned": 640, "removed": 120 }
    }
  ]
}

Enroll contacts in the win-back workflow

POST/api/list-health/win-back

Enrolls the given contacts into the account's win-back workflow, creating the workflow if it does not already exist. Contacts already enrolled are skipped.

Request body

contactIdsUUID[]bodyrequired

Contact IDs to enroll (1–1000).

Response fields

successboolean

true when enrollment completed.

dataobject

workflowId, workflowCreated (whether the workflow was created by this call), enrolled (newly enrolled), and alreadyEnrolled.

curl -X POST /api/list-health/win-back \
  -H "Content-Type: application/json" \
  -d '{ "contactIds": ["con_...","con_..."] }'
{
  "success": true,
  "data": {
    "workflowId": "7c1b2a3d-...",
    "workflowCreated": false,
    "enrolled": 2,
    "alreadyEnrolled": 0
  }
}

Contacts health check (scoped)

POST/api/contacts/health/run

Runs the contact-level health scan synchronously (sunset detection). Pass dryRun to preview.

Request body

dryRunbooleanbodydefault: false

When true, reports counts without mutating contacts.

Response fields

successboolean

true when the run completed. The response spreads the run stats alongside success: scanned, unengaged_detected, sunset_warned, sunset_reduced, and sunset_removed.

curl -X POST /api/contacts/health/run \
  -H "Content-Type: application/json" \
  -d '{ "dryRun": false }'
{
  "success": true,
  "scanned": 12400,
  "unengaged_detected": 980,
  "sunset_warned": 640,
  "sunset_reduced": 210,
  "sunset_removed": 120
}

Contacts health stats (scoped)

GET/api/contacts/health/stats

Returns contact-level sunset counters.

Response fields

unengaged_totalinteger

Total contacts currently flagged unengaged.

sunset_warnedinteger

Contacts in the warned stage.

sunset_reducedinteger

Contacts in the reduced stage.

sunset_removedinteger

Contacts that have been removed/suppressed.

duplicatesinteger

Detected duplicate contacts.

win_back_activeinteger

Contacts currently enrolled in the win-back workflow.

last_run_atstring | null

ISO-8601 time of the most recent contacts-health run.

curl /api/contacts/health/stats
{
  "unengaged_total": 980,
  "sunset_warned": 640,
  "sunset_reduced": 210,
  "sunset_removed": 120,
  "duplicates": 34,
  "win_back_active": 460,
  "last_run_at": "2026-05-27T06:00:00Z"
}

List unengaged contacts

GET/api/contacts/health/unengaged

Returns unengaged contacts as a plain array (no wrapper), using offset-based paging.

Query parameters

limitintegerquerydefault: 25

Results to return (max 100).

offsetintegerquerydefault: 0

Number of results to skip.

Response

Returns a JSON array of contacts, each with id, email, last_engagement_at, sunset_stage, and engagement_tier.

curl "/api/contacts/health/unengaged?limit=50&offset=0"
[
  {
    "id": "con_01hvghi",
    "email": "user@example.com",
    "last_engagement_at": "2026-01-10T09:22:00Z",
    "sunset_stage": "warned",
    "engagement_tier": "dormant"
  }
]

Contact scoring rules

Scoring rules define how lead-engagement points are awarded or subtracted when contact events occur.

GET/api/contacts/scoring-rules

Returns all scoring rules for the account.

Response fields

dataArray<Rule>

Each rule includes id, name, description, event_type, condition, score_delta, is_active, priority, and timestamps.

{
  "data": [
    {
      "id": "rule_01hv...",
      "name": "Opened an email",
      "description": "Award points when a contact opens an email",
      "event_type": "email_opened",
      "condition": {},
      "score_delta": 5,
      "is_active": true,
      "priority": 100
    }
  ]
}

Create a scoring rule

POST/api/contacts/scoring-rules

Create a scoring rule.

Request body

namestringbodyrequired

Rule name (1–100 chars).

event_typestringbodyrequired

One of email_opened, email_clicked, email_bounced, unsubscribed, form_submitted, tag_added, days_inactive, purchase_made.

score_deltaintegerbodyrequired

Points to add (negative subtracts), −100 to 100.

descriptionstringbody

Optional description (≤500 chars).

conditionobjectbodydefault: {}

Optional match condition for the event.

is_activebooleanbodydefault: true

Whether the rule is active.

priorityintegerbodydefault: 0

Evaluation priority (0–1000).

Response fields

dataobject

The created rule.

curl -X POST /api/contacts/scoring-rules \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Clicked a link",
    "event_type": "email_clicked",
    "score_delta": 10,
    "priority": 100
  }'
{
  "data": {
    "id": "rule_01hv...",
    "name": "Clicked a link",
    "event_type": "email_clicked",
    "score_delta": 10,
    "is_active": true,
    "priority": 100
  }
}

Update a scoring rule

PATCH/api/contacts/scoring-rules

Update a rule. Pass the rule id in the body with any fields to change.

Request body

idUUIDbodyrequired

ID of the rule to update.

Any field accepted by POST (name, event_type, score_delta, description, condition, is_active, priority) may also be sent to update it.

Response fields

dataobject

The updated rule.

curl -X PATCH /api/contacts/scoring-rules \
  -H "Content-Type: application/json" \
  -d '{ "id": "rule_01hv...", "score_delta": 15 }'

Delete a scoring rule

DELETE/api/contacts/scoring-rules

Delete a rule. The rule id is passed as a query parameter.

Query parameters

idUUIDqueryrequired

ID of the rule to delete.

Response fields

successboolean

true when the rule was deleted.

curl -X DELETE "/api/contacts/scoring-rules?id=rule_01hv..."
{ "success": true }

Status codes

  • 200 — OK. Run, stats, list, update, or delete succeeded.
  • 201 — Created. Scoring rule created.
  • 400 — Bad request. Invalid input (Invalid input), invalid rule id, or invalid JSON body.
  • 401 — Unauthorized. No active session.
  • 404 — Not found. Scoring rule not found.
  • 422 — Unprocessable entity. Scoring-rule body failed schema validation.

Recommended win-back flow

  1. Run POST /api/list-health/run (or dryRun: true first) to refresh sunset stats.
  2. Check GET /api/list-health/stats — note currentStats.unengaged and the sunset counters.
  3. Pull the unengaged contacts via GET /api/contacts/health/unengaged.
  4. Enroll the ones worth recovering with POST /api/list-health/win-back.
  5. Re-run the health check later — contacts who re-engage advance out of the sunset stages.

Contacts who do not respond to the win-back workflow are candidates for suppression. Continuing to send to chronically unengaged addresses harms your sender reputation.