Skip to main content

Alerts

Standing searches on a cadence. Configuration is free; delivered records bill at the standard rate.

Lead generation and business developmentHomebuilders and developers

An alert is a saved filter set plus a cadence. Each run produces a numbered delivery — a batch of records with its own charge — which you can then read.

The endpoints

EndpointWhat it does
POST /alertsCreate a rule — optionally shared with your team via share. Free.
GET /alertsList the rules your membership resolves — yours plus the ones shared with you. Free.
GET /alerts/{id}Read one rule, including its live share membership. Free.
PATCH /alerts/{id}Change filters, cadence, budget, recipients or sharing. Any member may edit. Free.
DELETE /alerts/{id}Remove a rule — for every member it is shared with. Free.
POST /alerts/{id}/previewHow many records would this deliver, and what would it cost? Free, and delivers nothing.
POST /alerts/{id}/runTrigger a real run now. Billed like any delivery, and capped per day.
GET /alerts/{id}/deliveriesThe run history with per-run counts and charges. Free.
GET /alerts/{id}/matchesThe records from a delivery. Free — the run already charged for them, so re-reading costs nothing.
POST|DELETE /alerts/{id}/dismissals/{projectId}Stop a match being re-offered. Free.

Create a daily alert — configuration is free

bash
curl -X POST 'https://api.boardwalkai.com/api/v1/alerts' \
  -H 'Authorization: Bearer bwk_live_YOUR_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Utah apartments, 50+ units",
    "filters": {
        "states": [
            "UT"
        ],
        "assetClasses": [
            "Apartment Building"
        ],
        "totalUnitCountMin": 50
    },
    "cadence": {
        "frequency": "daily",
        "timezone": "America/Denver",
        "hourLocal": 7
    },
    "maxRecordsPerRun": 100,
    "notify": {
        "email": true,
        "emailTo": [
            "leads@example.com"
        ]
    }
}'

Response · HTTP 201

json
{
  "data": {
    "id": "alr_01K1QF3M0000ALERT000001",
    "name": "Utah apartments, 50+ units",
    "status": "active",
    "filters": {
      "states": [
        "UT"
      ],
      "assetClasses": [
        "Apartment Building"
      ],
      "totalUnitCountMin": 50
    },
    "specificRequirement": null,
    "deliveryMode": "all",
    "cadence": {
      "frequency": "daily",
      "timezone": "America/Denver",
      "hourLocal": 7,
      "backfillDays": 7,
      "nextRunAt": "2026-08-03T07:00:00-06:00",
      "lastWindowTo": null
    },
    "budget": {
      "maxRecordsPerRun": 100
    },
    "notify": {
      "email": true,
      "emailTo": [
        "leads@example.com"
      ],
      "webhookUrl": null
    },
    "share": {
      "mode": "private",
      "members": []
    },
    "consecutiveFailures": 0,
    "createdAt": "2026-08-02T14:12:00+00:00",
    "updatedAt": "2026-08-02T14:12:00+00:00"
  },
  "meta": {
    "creditsUsed": 0
  }
}

The response shape on this page is the one the endpoint builds — the keys, their nesting and their types are asserted against the shipping code by a test. The numbers inside it are the scenario being walked through, not a measurement of the corpus.

What each run delivered, and what it charged

bash
curl 'https://api.boardwalkai.com/api/v1/alerts/alr_01K1QF3M0000ALERT000001/deliveries?limit=2' \
  -H 'Authorization: Bearer bwk_live_YOUR_KEY'

Response · HTTP 200

json
{
  "data": [
    {
      "id": "dlv_01K1QF3M0000DELIVERY002",
      "alertId": "alr_01K1QF3M0000ALERT000001",
      "sequence": 2,
      "status": "delivered",
      "window": {
        "from": "2026-08-01T07:00:00-06:00",
        "to": "2026-08-02T07:00:00-06:00"
      },
      "counts": {
        "matched": 9,
        "delivered": 9,
        "billable": 7
      },
      "credits": {
        "unit": "record",
        "rate": 1,
        "charged": 7,
        "remaining": 286
      },
      "matchesUrl": "/api/v1/alerts/alr_01K1QF3M0000ALERT000001/matches?deliveryId=dlv_01K1QF3M0000DELIVERY002"
    }
  ],
  "meta": {
    "limit": 2,
    "offset": 0,
    "creditsUsed": 0
  }
}

The response shape on this page is the one the endpoint builds — the keys, their nesting and their types are asserted against the shipping code by a test. The numbers inside it are the scenario being walked through, not a measurement of the corpus.

Natural-language criteria

specificRequirement (up to 500 characters) puts a free-text requirement on the alert — "projects that mention a lagoon or resort-style amenity" — matched through the same evidence-verified pipeline as AI-search jobs. It can stand alone (no filters at all) or narrow a structured filter set, and each NL-matched record from /alerts/{alertId}/matches carries whyThisMatched plus verbatim matchEvidence. An alert with no geography at all is a nationwide standing instruction and needs a plan whose licensed geography is nationwide (API Only, National); every other plan must name its geography.

The caps

CapValueWho it applies to
Active alerts per account25Everyone. It is a billing cap, so it counts every alert on the account — including ones not shared with you. 403 alert_limit_reached; GET /alerts reports meta.activeAlerts.
States per alert5Only plans narrower than nationwide. API Only and National pools are exempt and may hold a nationwide alert. 403 alert_geography_too_broad otherwise.
Counties + cities per alert25Same exemption. Counties and cities share one budget.
Records materialised per run100 by default, up to 1000Everyone. Set maxRecordsPerRun on the alert; a run stops there rather than delivering an unbounded batch.

Running one by hand

POST /alerts/{alertId}/run requires an Idempotency-Key and is capped at 3 manual runs per alert per calendar day of the service clock (US Mountain Time, America/Denver) — the 4th returns 429 manual_runs_exhausted with Retry-After and error.details.resetsAt naming that clock's next midnight. Read those two rather than computing the boundary yourself. Replaying a key that already ran returns the original delivery at meta.creditsUsed: 0 and does not spend an attempt; scheduled cadence runs are never counted against the cap.

Push delivery: the alert webhook

Set notify.webhookUrl (https only) and every completed run POSTs an alert.delivered event to it — the one piece of push delivery on the surface. It is a pointer, never records: id, type, createdAt, apiVersion, and a data block with alertId, deliveryId, sequence, counts (delivered, billed, notRebilled), credits (charged, remaining) and matchesUrl. Fetch the records from that URL with your API key, free.

The POST carries a Boardwalk-Signature header — hmac_sha256(body, deliveryId) — and gives up after 5 seconds. There is no endpoint-registration or shared-secret flow yet, so treat that header as an integrity marker rather than authentication: it proves the body belongs to the delivery it names, not that we sent it. Authenticate the fetch, not the ping. A failed POST is logged and not retried — the delivery still exists, so a cadence poll of GET /alerts/{alertId}/deliveries is the safety net.

Sharing, and who can see what

An alert belongs to its membership, not to the whole account. share.mode is private (default — only you), team (your current team roster, dynamically: people who join later are included, and share.optOutUserIds carves individuals out), or custom (exactly share.userIds, plus you — ids from your own team roster only; sharing across accounts is impossible). Every member holds the edit surface symmetrically: read, edit, run, dismiss, undo. Delete is the one exception: only the alert's owner or the team's admin may DELETE /alerts/{alertId} (any other member gets 403 forbidden), and a delete archives the alert for everyone, keeping its delivery history readable. Dismissals are one living state for the whole membership, attributed to whoever dismissed. Anything your membership does not resolve is a plain 404, on every route.

Sharing needs a team on a paid plan — share.mode of team or custom without one is a 403 sharing_requires_paid_plan, and the refused create or update is rolled back whole. Sharing does not change billing: Find Contact Info still spends the same team pool, whoever triggered it.

Email is a notification, not the payload

notify.email tells people that a delivery happened. The records come from the API. Building against the email would mean parsing a message we reserve the right to redesign.

Ready to make a call?

A free Boardwalk trial includes API access, 50 record-export credits (search, CSV, MCP), and 5 Find Contact Info lookups. Counting, taxonomy, location lookups and the analytics plane cost nothing, so you can evaluate the data before you spend a credit.