API reference

Mailing lists & subscribers

Manage your audience: lists, the subscribers on them, segments, and custom fields. Paths are relative to https://app.blacklistguard.com/api/v1.

Lists

GET    /mailing-lists
POST   /mailing-lists
PUT    /mailing-lists/{id}
DELETE /mailing-lists/{id}
GET    /mailing-lists/stats?window=30

Create a list with:

FieldDescription
nameList name.
descriptionOptional description.
from_name / from_emailDefault sender for campaigns to this list.
reply_to_emailDefault reply-to.
opt_in_typeOpt-in label for your reference: single, double, api, or import.

stats returns per-list metrics over the window: new_subscribers, unsubscribed, net_growth, engagement_rate, bounce_rate, complaint_rate (rates are 0–1 fractions), health_score (0–100), a daily sparkline, and the last_campaign. Match each row to a list by id.

Subscribers (per list)

GET    /mailing-lists/{id}/subscribers?page&limit&status&search
POST   /mailing-lists/{id}/subscribers
PUT    /mailing-lists/{id}/subscribers/{subscriber_id}
DELETE /mailing-lists/{id}/subscribers/{subscriber_id}
PUT    /mailing-lists/{id}/subscribers/{subscriber_id}/status
DELETE /mailing-lists/{id}/subscribers/all

Add a subscriber with { email, first_name, last_name, source, custom_fields }. On update, email is not editable; you can change first_name, last_name, status, and custom_fields. status is one of active, unsubscribed, bounced, complained.

ℹIn the list endpoints, {subscriber_id} is the subscriber's UUID (not the membership row id), and each returned row's custom_fields is a JSON string, so parse it before use.

Import & export

POST /mailing-lists/{id}/subscribers/import-async   (multipart, field "csvFile")
POST /mailing-lists/{id}/subscribers/export-async

Both run as background jobs and return a job_id. Track progress via the jobs endpoints. The import CSV needs a header row with an Email column; First/Last/Status are optional and extra columns map to the list's defined custom fields.

Subscribers (workspace-wide)

GET /subscribers?page&limit&status&search&list_id
GET /subscribers/{id}
GET /subscribers/{id}/segments

Lists every subscriber across the workspace with a summary and pagination (here custom_fields is a JSON object). get(id) returns full detail: identity, the lists they're on, lifetime stats, an engagement score, recent activity, and their bounces.

Segments

GET    /segments?page&limit&search&list_id
GET    /mailing-lists/{id}/segments
POST   /mailing-lists/{id}/segments
PUT    /mailing-lists/{id}/segments/{segment_id}
DELETE /mailing-lists/{id}/segments/{segment_id}
POST   /mailing-lists/{id}/segments/preview
GET    /mailing-lists/{id}/segments/{segment_id}/subscribers

A segment is a saved filter on a list. Create one with { name, description, conditions, is_active }. conditions uses a recursive { logic, rules } shape, where logic is "and" or "or", and each rule is either a condition { field, operator, value } or a nested group with the same { logic, rules } shape:

{
  "logic": "and",
  "rules": [
    { "field": "country", "operator": "equals", "value": "US" },
    { "logic": "or", "rules": [
      { "field": "plan", "operator": "equals", "value": "growth" },
      { "field": "plan", "operator": "equals", "value": "scale" }
    ] }
  ]
}

preview takes the same conditions and returns { subscriber_count } without saving. Note is_active defaults to false unless you set it.

A segment can match subscribers of any status, so a rule like status is unsubscribed works for counting and exporting. Campaigns and autoresponders still only send to active members, whatever the segment says.

Rule operators

OperatorValueUse for
equals, not_equals, contains, starts_with, ends_withstringText fields
greater_than, less_than, greater_than_or_equal, less_than_or_equalnumberNumeric fields
before, afterdate / datetime stringDate fields
in_last_days, not_in_last_daysinteger (days)Date fields
inarray of stringsText / select
is_one_of, is_not_one_ofarray of stringse.g. email-domain match
ends_with_any_of, does_not_end_with_any_ofarray of stringsSuffix match
is, is_notscalarStatus / boolean fields
is_in, is_not_ina segment idSegment membership
is_active_in, is_not_active_ina mailing-list idActive membership in a list

Portion rule

Besides conditions and groups, a rule can pick a share of the list. The share is random, but stable: each subscriber's place is fixed by their own ID and a key you choose, so people joining or leaving the list never move anyone else.

{ "type": "portion", "lower": 0, "upper": 20, "key": "ABCDE" }
FieldTypeDescription
lowernumberStart of the range, a percentage from 0 to 100 with at most 2 decimals. Included.
uppernumberEnd of the range, on the same scale. Excluded, unless it is 100. Must be above lower.
keystring1 to 50 characters. Leave it out when you save a segment or a campaign's own conditions, and we generate one and return it in the saved conditions.

Because upper is excluded, 0 to 50 and 50 to 100 never overlap, and 0 to 100 is the whole list. The same key picks the same subscribers wherever you use it, and rules that share a key with ranges that don't overlap always pick different subscribers. A different key picks an unrelated set.

You can use it on its own, inside a group, or next to other conditions. For example, 20% of the list who use Gmail:

{
  "logic": "and",
  "rules": [
    { "type": "portion", "lower": 0, "upper": 20, "key": "ABCDE" },
    { "field": "email", "operator": "contains", "value": "@gmail.com" }
  ]
}

preview doesn't save anything, so it doesn't generate a key. Include one, or the preview returns 400. A range or key that isn't valid also returns 400 BAD_REQUEST, with a message that says what's wrong, for example invalid segment rule: portion lower must be below upper.

This lets you split-test with separate campaigns: send version A to 0 to 10 and version B to 10 to 20, both with the same key, then send the better one to 20 to 100 with that key again. Nobody receives more than one. See Split tests across separate campaigns. To test versions inside a single campaign, use an A/B test instead.

Custom fields

GET    /mailing-lists/{id}/custom-fields
POST   /mailing-lists/{id}/custom-fields
PUT    /mailing-lists/{id}/custom-fields/{field_id}
DELETE /mailing-lists/{id}/custom-fields/{field_id}

GET    /custom-fields/global   (workspace-wide fields)

Required: field_name, field_label, field_type. The field_name is normalized (lowercased, spaces become underscores) and locked once created. field_type is one of text, textarea, number, date, email, url, boolean, select, multiselect; for select and multiselect you must also supply field_options (a JSON array of choices). Optional: placeholder, default_value, help_text, validation_rules, is_required, is_active, display_order. Global fields apply to every list.