Skip to content

Developers

API reference

All 110 endpoints of the relaybell REST API, with the scope each needs, its parameters, responses, error codes and examples. Generated from the OpenAPI 3.1.0 document (version 1.2.0).

Base URL https://relaybell.com/api/v1

Jump to an endpoint

Overview

The relaybell REST API: send web push notifications, track events, and manage segments, subscribers, webhooks and automations for a website. Base URL: https://relaybell.com/api/v1.

Authentication. Send a secret API key (it starts with rb_live_) as Authorization: Bearer rb_live_…. Create keys in the dashboard under Settings → Developers → API keys; a key belongs to one website, so calls need no project id. Each key has scopes: an operation's x-scope names the one it needs (full allows everything; reads without a scope work with any key). Keep keys on your server, never in browser code. The routes the browser SDK calls (Browser SDK) take your public Project ID instead. A few routes work only from the signed-in dashboard; they say so.

Errors. Every error is JSON { "error": "code", "message": "…", "request_id": "…" } with a standard HTTP status (message is not always there). Branch on error, never on message. Every response carries the same id in X-Request-Id — quote it when you contact support. A body that isn't valid JSON answers 400 invalid_json; bodies are limited to 1 MB (413 payload_too_large). Every error code: relaybell.com/docs#errors.

Rate limits.

Requests Limit Counted per
POST /notifications and /notifications/{id}/send (saving a draft isn't counted) 60 a minute and 600 an hour API key (dashboard: account)
Broadcasts: sends to everyone, a segment, a topic or filters 30 per clock hour website
Sends to specific users (externalId / externalIds), or transactional with a segment or filters 1,000 per clock hour website
Test sends to your own devices 120 per clock hour website
POST / GET /events with an API key 3,000 a minute API key
Webhook resends and test events 30 a minute website
Browser SDK routes (/projects/…, click and delivery pings) and /events without a key 120 a minute visitor IP
POST /ai/suggest 30 an hour IP address
POST /upload 60 an hour IP address

Going over a request limit answers 429 rate_limited; going over an hourly send budget answers 429 send_limit_reached with limit, kind and retryAfter. Limited routes send RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset; every 429 sends Retry-After (seconds) — wait that long instead of retrying in a loop. Other API-key routes have no request limit of their own.

SDKs. Server SDKs 1.2.0: npm relaybell, PyPI relaybell, Composer relaybell/relaybell-php. The full guide, with the browser SDK, merge tags and webhook signatures: relaybell.com/docs.

Notifications

Send to everyone, a segment, a topic or one of your users; schedule, cancel, keep drafts, and read how each send did.

post/notifications

Send a notification

Scope: sendoperationId: sendNotification

Accepts the send and answers 202; delivery happens in the background. With no targeting fields it goes to every subscriber of the website. Send to one of your own users with externalId (or externalIds), usually with transactional: true; see Send to one user.

With "draft": true it saves a draft instead (201, never sent or counted against limits; a website keeps up to 500 drafts of up to 256 KB each).

Until the account's email address is verified it answers 403 email_not_verified. Limits: 60 a minute and 600 an hour per API key (429 rate_limited), and per website 30 broadcasts and 1,000 sends to specific users per clock hour (429 send_limit_reached, with Retry-After).

Request body · application/json

  • titlestring

    Notification title. Required to send (400 title_required). Supports merge tags such as {name|there}.

  • bodystring

    Message text. Supports merge tags.

  • urlstring

    Page opened on click. Left out, your Default link is used; your UTM tags are added. Supports merge tags.

  • iconstring

    Small image (URL). Left out, your default notification logo is used.

  • imagestring

    Large picture (URL). Shown mainly by Chrome and Edge on Windows and Android.

  • badgestring

    Small one-colour icon (URL of a PNG with a transparent background, about 96×96 px) for the Android status bar.

  • actionsarray of Action

    Up to 2 buttons.

    Range: at most 2.

  • actions[].actionstring

    Button id (defaults to btn1 / btn2).

  • actions[].titlestringrequired

    Button text (required).

  • actions[].urlstring

    Opened when the button is clicked; kept only when it is an http(s) link.

  • requireInteractionboolean

    Keep the notification on screen until the person clicks or closes it (Chrome and Edge on computers).

  • tagstring

    A label: a new notification with the same tag replaces one still showing.

  • externalIdstring

    Send to one of your own users: the user id the browser SDK linked (externalId in init(), or identify()). Exact match, case matters.

  • externalIdsarray of string

    The same for a list. Used instead of externalId when both are sent. All empty: 400 external_ids_empty (never everyone).

  • transactionalboolean

    A message a person is waiting for: goes out even during quiet hours, skips the frequency cap, never listed in the public inbox.

  • segmentIdstring (uuid)

    Target a saved segment. Not this website's: 400 segment_not_found.

  • filtersFilters

    The segment filter format, shared by segments, inline send filters / excludeFilters, schedules and the audience list.

  • filters.matchstring

    all = every rule must match (default), any = at least one.

    One of: all, any. Default "all".

  • filters.rulesarray of Rule

    The rules.

  • rules[].fieldstringrequired

    country (2-letter upper-case code from the visitor's IP), language, url (the subscriber's last page), external_id (or externalId), or tag:KEY for one of your tags. An unknown field matches nobody.

  • rules[].opstring

    eq (default; also used for an unknown op), neq, contains (case-insensitive), in (value is a list), exists, not_exists. For a tag, neq also matches subscribers without that tag.

    One of: eq, neq, contains, in, exists, not_exists. Default "eq".

  • rules[].valuestring or array of string

    Compared as text, exactly: eq, neq and in are case-sensitive (write country codes in upper case). Not needed for exists / not_exists.

  • topicIdstring

    Only subscribers of this topic: its id or its slug (any case). Unknown: 400 unknown_topic.

  • excludeSegmentIdstring (uuid)

    Leave this segment out. Unknown: 400 segment_not_found.

  • excludeFiltersFilters

    The segment filter format, shared by segments, inline send filters / excludeFilters, schedules and the audience list.

  • excludeFilters.matchstring

    all = every rule must match (default), any = at least one.

    One of: all, any. Default "all".

  • excludeFilters.rulesarray of Rule

    The rules.

  • rules[].fieldstringrequired

    country (2-letter upper-case code from the visitor's IP), language, url (the subscriber's last page), external_id (or externalId), or tag:KEY for one of your tags. An unknown field matches nobody.

  • rules[].opstring

    eq (default; also used for an unknown op), neq, contains (case-insensitive), in (value is a list), exists, not_exists. For a tag, neq also matches subscribers without that tag.

    One of: eq, neq, contains, in, exists, not_exists. Default "eq".

  • rules[].valuestring or array of string

    Compared as text, exactly: eq, neq and in are case-sensitive (write country codes in upper case). Not needed for exists / not_exists.

  • sendAtstring (date-time)

    ISO 8601 time to schedule the send. In the past, or unreadable: sends now.

  • perTimezoneboolean

    Deliver at localTime in each subscriber's time zone (sendAt is ignored).

  • localTimestring

    "H:MM" or "HH:MM", with perTimezone.

  • smartTimeboolean

    Deliver at each subscriber's most-active hour (sendAt is ignored).

  • ttlinteger

    Seconds the push service keeps trying an offline device. Default 24 hours, at most 28 days (2419200).

    Range: 1–2419200.

  • translationsmap of object

    Per-language copy keyed by two-letter language: { "es": { "title", "body" } }.

  • translations.{key}.titlestring
  • translations.{key}.bodystring
  • abobject

    A/B test: each browser gets version A (the main fields) or B. Ignored unless b has at least one field.

  • ab.bobjectrequired

    Version B; fields left out come from A.

  • b.titlestring
  • b.bodystring
  • b.urlstring
  • b.imagestring
  • abTestobject

    Auto-winner, together with ab.

  • abTest.autoWinnerboolean

    Send both versions to a test group, then the winner to the rest.

  • abTest.testPercentinteger

    Test group size, 1–90 (default 20).

    Default 20. Range: 1–90.

  • abTest.afterHoursinteger

    Hours before the winner goes out, 1–168 (default 4).

    Default 4. Range: 1–168.

  • draftboolean

    true saves a draft instead of sending (201). The title may then be empty and nothing is checked yet.

Responses

  • 201Draft saved (draft: true).
    Response fields
    • oktruerequired
    • notificationIdstring (uuid)required
    • status"draft"required
  • 202Accepted.
    Response fields
    • oktruerequired
    • notificationIdstring (uuid)required

      Keep it: the send appears under this id in reports and webhooks, and it is how you cancel it.

    • statusstringrequired

      queued (sending starts now), scheduled (a future sendAt, or held until your quiet hours end) or held_for_review (a new account's first large broadcast; you can cancel it meanwhile).

      One of: queued, scheduled, held_for_review.

    • messagestring

      Only with held_for_review: what happens next.

  • 400Bad request

    invalid_jsontitle_requiredunknown_fieldexternal_ids_emptyunknown_topicsegment_not_found

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    email_not_verifiedaccount_suspendedinsufficient_scope

  • 409Conflict

    too_many_drafts

  • 413Too large

    draft_too_largepayload_too_large

  • 429Too many requests

    rate_limitedsend_limit_reached

Example

curl -X POST https://relaybell.com/api/v1/notifications \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "title": "Flash sale 🔥",
    "body": "30% off ends tonight — {name|there}!",
    "url": "https://shop.example/sale"
  }'

Example: one user

curl -X POST https://relaybell.com/api/v1/notifications \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "externalId": "user_123",
    "transactional": true,
    "title": "Your order has shipped",
    "body": "Order #1042 is on its way.",
    "url": "https://shop.example/orders/1042"
  }'
get/notifications

List notifications

Any API keyoperationId: listNotifications

Newest first, 25 per page (limit up to 100). Without status, everything except drafts is listed. For the next page, send the nextCursor you got as cursor, with the same filters; pages don't shift when new sends arrive.

Query parameters

  • fromstring (date)

    First day (YYYY-MM-DD, in tz) a notification was made on.

  • tostring (date)

    Last day (YYYY-MM-DD, included).

  • statusstring

    One or more of draft, scheduled, queued, held_for_review, sending, sent, failed, canceled, separated by commas.

  • qstring

    Searches titles and texts, in any case.

  • tzstring

    IANA time zone for from / to (default: the website's).

  • limitinteger

    Items per page, 1–100 (default 25).

    Default 25.

  • cursorstring

    The nextCursor from the previous page (send the same filters again). Anything else answers 400 invalid_cursor.

Responses

  • 200A page of notifications.
    Response fields
    • itemsarray of NotificationListItemrequired
    • items[].idstring (uuid)required
    • items[].titlestringrequired
    • items[].bodystring or null
    • items[].statusstringrequired

      draft, scheduled, queued, held_for_review, sending, sent, failed (reason in error) or canceled.

      One of: draft, scheduled, queued, held_for_review, sending, sent, failed, canceled.

    • items[].created_atstring (date-time)required
    • items[].scheduled_atstring (date-time) or null

      When a scheduled send goes (or went) out; null for one that started straight away.

    • items[].recipientsintegerrequired

      Browsers targeted.

    • items[].sentintegerrequired

      Pushes the push services accepted.

    • items[].deliveredintegerrequired

      Pushes a device confirmed it showed.

    • items[].failedinteger

      Pushes that failed.

    • items[].prunedinteger

      Expired subscriptions found and removed during the send.

    • items[].clickedintegerrequired
    • items[].ctrnumberrequired

      clicked ÷ sent, as a percentage with one decimal.

    • items[].conversionsinteger
    • items[].revenuenumber
    • items[].abboolean

      Whether it was an A/B test.

    • items[].targetSummarystring

      Who it went to, in words: "All subscribers", a segment's name, "Topic: News", "Specific users", …

    • items[].automationboolean

      Whether an automation, a journey or a product alert made it.

    • items[].errorstring or null

      Why a failed send never went out (email_not_verified, suspended, segment_missing, review_rejected, dispatch_error), else null.

    • nextCursorstring or nullrequired

      Send as cursor (with the same filters) for the next page; null on the last page.

  • 400Bad request

    invalid_statusinvalid_rangeinvalid_tzinvalid_cursor

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

curl "https://relaybell.com/api/v1/notifications?status=sent&limit=25" \
  -H "Authorization: Bearer rb_live_xxxxx"
get/notifications/export.csv

Export notifications as CSV

Any API keyoperationId: exportNotificationsCsv

The same filters as the list (no cursor), the same rows in the same order, up to 10,000. Columns: id, created_at, scheduled_at, status, title, target, recipients, sent, failed, clicked, ctr, conversions, revenue. A title starting with =, +, - or @ gets a leading ' so a spreadsheet doesn't run it.

Query parameters

  • fromstring (date)

    First day (YYYY-MM-DD).

  • tostring (date)

    Last day (YYYY-MM-DD, included).

  • statusstring

    Comma-separated statuses.

  • qstring

    Search in titles and texts.

  • tzstring

    IANA time zone for the days.

Responses

  • 200The CSV file.text/csv
  • 400Bad request

    invalid_statusinvalid_rangeinvalid_tz

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/notifications/export.csv" \
  -H "Authorization: Bearer rb_live_xxxxx"
get/notifications/{id}

Get one notification

Any API keyoperationId: getNotification

Everything stored about one notification (or draft — its sendBody holds the saved fields), and how it did.

Path parameters

  • idstring (uuid)required

    The notification's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

Responses

  • 200The notification and its report.
    Response fields
    • notificationobjectrequired
    • notification.idstring (uuid)required
    • notification.titlestringrequired
    • notification.bodystring or null
    • notification.urlstring or null
    • notification.iconstring or null
    • notification.imagestring or null
    • notification.badgestring or null
    • notification.actionsarray of Action or null
    • notification.require_interactionboolean
    • notification.tagstring or null
    • notification.statusstringrequired

      draft, scheduled, queued, held_for_review, sending, sent, failed (reason in error) or canceled.

      One of: draft, scheduled, queued, held_for_review, sending, sent, failed, canceled.

    • notification.created_atstring (date-time)required
    • notification.scheduled_atstring (date-time) or null
    • notification.targetobject or null

      How the people were chosen ({ segmentId | filters, topicId, excludeSegmentId | excludeFilters, … }); null = everyone.

    • notification.targetSummarystring
    • notification.automationboolean
    • notification.automation_idstring (uuid) or null
    • notification.transactionalboolean
    • notification.abobject or null
    • notification.ab_testobject or null
    • notification.translationsobject or null
    • notification.ttlinteger or null
    • notification.per_timezoneboolean
    • notification.local_timestring or null

      "HH:MM", "smart", or null.

    • notification.statsobject or null
    • notification.errorstring or null
    • notification.recipientsinteger
    • notification.processedinteger
    • notification.sentinteger
    • notification.deliveredinteger
    • notification.failedinteger
    • notification.prunedinteger
    • notification.clickedinteger
    • notification.ctrnumber
    • notification.conversionsinteger
    • notification.revenuenumber
    • funnelobjectrequired
    • funnel.recipientsinteger
    • funnel.sentinteger
    • funnel.deliveredinteger
    • funnel.clickedinteger
    • funnel.convertedinteger
    • funnel.failedinteger
    • funnel.expiredinteger
    • devicesarray of objectrequired

      sent and clicked by device type (empty once data retention removed the send's event log).

    • devices[].devicestring
    • devices[].sentinteger
    • devices[].clickedinteger
    • variantsarray of objectrequired

      For an A/B send, versions a and b; otherwise empty.

    • variants[].variantstring

      One of: a, b.

    • variants[].sentinteger
    • variants[].clickedinteger
    • variants[].ctrnumber
    • languagesarray of objectrequired
    • languages[].languagestring

      Two letters, or unknown.

    • languages[].sentinteger
    • languages[].clickedinteger
    • errorsarray of objectrequired
    • errors[].codestring

      What the push service answered (an HTTP status, timeout, …), or expired.

    • errors[].countinteger
    • revenuenumberrequired
    • currencystring or nullrequired

      The website's revenue currency, or null.

    • sendBodyobject or nullrequired

      The POST /notifications body that makes this send again, or a draft's saved fields. null for automation, journey and product-alert sends.

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

  • 404Not found

    not_found

Example

curl "https://relaybell.com/api/v1/notifications/5b0c2f7e-1d7a-4c3e-9a51-2f8e6b1d0c42" \
  -H "Authorization: Bearer rb_live_xxxxx"
patch/notifications/{id}

Change a draft

Scope: sendoperationId: updateDraft

Changes the fields you send; the others stay as they were. A field sent as null is cleared. Only a draft can be changed (409 not_a_draft).

Path parameters

  • idstring (uuid)required

    The draft's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

Request body · application/json

The same fields as Send a notification.

Responses

  • 200The draft now.
    Response fields
    • oktruerequired
    • notificationIdstring (uuid)required
    • status"draft"required
    • sendBodyobjectrequired

      The draft's fields now.

  • 400Bad request

    invalid_json

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

  • 409Conflict

    not_a_draft

  • 413Too large

    draft_too_largepayload_too_large

Example

cURL
curl -X PATCH https://relaybell.com/api/v1/notifications/ID \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "title": "Autumn sale — last day"
  }'
delete/notifications/{id}

Delete a draft

Scope: sendoperationId: deleteDraft

Only a draft can be deleted (409 not_a_draft).

Path parameters

  • idstring (uuid)required

    The draft's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

Responses

  • 200Deleted.
    Response fields
    • oktruerequired
    • deletedtruerequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

  • 409Conflict

    not_a_draft

Example

cURL
curl -X DELETE https://relaybell.com/api/v1/notifications/ID \
  -H "Authorization: Bearer rb_live_xxxxx"
post/notifications/{id}/send

Send a draft

Scope: sendoperationId: sendDraft

Sends the draft with exactly the checks, limits, quiet hours and sendAt scheduling of a normal send, and answers the same way. Fields in the body replace the draft's for this send. The notification keeps its id; its date becomes the moment it was sent. A sendAt that has passed sends now.

Path parameters

  • idstring (uuid)required

    The draft's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

Request body · application/json · optional

The same fields as Send a notification.

Responses

  • 202Accepted.

    The same answer as Send a notification.

  • 400Bad request

    invalid_jsontitle_requiredunknown_fieldexternal_ids_emptyunknown_topicsegment_not_found

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    email_not_verifiedaccount_suspendedinsufficient_scope

  • 404Not found

    not_found

  • 409Conflict

    not_a_draft

  • 413Too large

    payload_too_large

  • 429Too many requests

    rate_limitedsend_limit_reached

Example

cURL
curl -X POST https://relaybell.com/api/v1/notifications/ID/send \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "sendAt": "2026-11-01T09:00:00Z"
  }'
post/notifications/{id}/cancel

Cancel a scheduled send

Scope: sendoperationId: cancelNotification

Stops a send that is still scheduled (a future sendAt, or held for quiet hours) or held_for_review. A send that has already started (including a per-time-zone or smart-time send, which starts at once), has finished, was already canceled, doesn't exist or belongs to another website answers 404 not_scheduled.

Path parameters

  • idstring (uuid)required

    The notification id.

Responses

  • 200Canceled.
    Response fields
    • oktruerequired
    • canceledtruerequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_schedulednot_found

Example

curl -X POST https://relaybell.com/api/v1/notifications/5b0c2f7e-1d7a-4c3e-9a51-2f8e6b1d0c42/cancel \
  -H "Authorization: Bearer rb_live_xxxxx"

Events

Behavioral events from your server: conversions, automation triggers and product alerts.

post/events

Track an event

Scope: events:writeoperationId: trackEvent

A behavioral event from your server: it can count as a conversion, start or cancel automations, and enter people into journeys. Name the person with externalId (your user id) or subscriberId (relaybell's id); an event that names nobody is stored, but only a product alert can come of it.

Product alerts: back_in_stock or price_drop with a properties.productId and no externalId, subscriberId or endpoint goes to everyone interested in that product (see Product alerts). 3,000 requests a minute per API key.

Request body · application/json

  • eventstring

    The event name (or name). Cut to 120 characters; missing: 400 event_required.

  • namestring

    Another spelling of event.

  • externalIdstring

    Your user id.

  • subscriberIdstring (uuid)

    relaybell's subscriber id.

  • endpointstring

    Only disqualifies a product-alert broadcast; it does not identify the person on this route.

  • propertiesobject

    Anything; value or revenue (a number) makes it a conversion.

Responses

  • 202Stored.
    Response fields
    • oktruerequired
    • eventIdstringrequired

      The stored event's id.

    • subscriberIdstring (uuid) or nullrequired

      The subscriber it was linked to, or null.

    • isConversionbooleanrequired

      purchase, order.completed, checkout.completed (or your conversionEvents), or any event with a numeric value / revenue — except cart.updated and checkout.started.

    • attributionobject or nullrequired

      The notification credited with this conversion (last delivered in the attribution window), or null.

    • attribution.notificationIdstring (uuid)
    • attribution.valuenumber
    • automationsTriggeredintegerrequired
    • sequencesEnrolledintegerrequired
    • productAlertsinteger

      Product alerts created (back_in_stock / price_drop naming no person).

    • alertAudienceinteger

      People the product alerts reach.

    • alertAudienceTruncatedboolean

      The audience was cut at 100,000.

    • dedupedboolean

      A price_drop for this product already went out in the last 10 minutes.

  • 400Bad request

    invalid_jsonevent_required

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    payload_too_large

  • 429Too many requests

    rate_limited

Example

curl -X POST https://relaybell.com/api/v1/events \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "event": "purchase",
    "externalId": "user_123",
    "properties": {
      "value": 79.99,
      "orderId": "A123"
    }
  }'
get/events

List recent events

Scope: subscribers:readoperationId: listEvents

The latest 100 events, newest first. They name people, so this needs subscribers:read.

Responses

  • 200The events.
    Response fields
    • eventsarray of objectrequired
    • events[].idstringrequired
    • events[].namestringrequired
    • events[].external_idstring or null
    • events[].subscriber_idstring (uuid) or null
    • events[].propertiesobjectrequired
    • events[].created_atstring (date-time)required
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 429Too many requests

    rate_limited

Example

curl "https://relaybell.com/api/v1/events" \
  -H "Authorization: Bearer rb_live_xxxxx"

Segments

Saved filters to send to (or leave out of a send), and live counts.

get/segments

List segments

Any API keyoperationId: listSegments

Saved filters, newest first.

Responses

  • 200The segments.
    Response fields
    • segmentsarray of Segmentrequired
    • segments[].idstring (uuid)required
    • segments[].namestringrequired
    • segments[].filtersFiltersrequired

      The segment filter format, shared by segments, inline send filters / excludeFilters, schedules and the audience list.

    • segments[].created_atstring (date-time)required
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

curl "https://relaybell.com/api/v1/segments" \
  -H "Authorization: Bearer rb_live_xxxxx"
post/segments

Create a segment

Scope: segments:writeoperationId: createSegment

A segment needs a name and at least one complete rule (a field — a tag: field with its key — and a value, unless the op is exists / not_exists): a segment with no rules would include every subscriber.

Request body · application/json

  • namestringrequired

    Required (400 name_required).

  • filtersFiltersrequired

    The segment filter format, shared by segments, inline send filters / excludeFilters, schedules and the audience list.

  • filters.matchstring

    all = every rule must match (default), any = at least one.

    One of: all, any. Default "all".

  • filters.rulesarray of Rule

    The rules.

    Its fields: see Send a notification.

Responses

  • 201Created.
    Response fields
  • 400Bad request

    invalid_jsonname_requiredsegment_needs_a_rule

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    payload_too_large

Example

curl -X POST https://relaybell.com/api/v1/segments \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "name": "Indian Pro users",
    "filters": {
      "match": "all",
      "rules": [
        {
          "field": "country",
          "op": "in",
          "value": [
            "IN"
          ]
        },
        {
          "field": "tag:plan",
          "op": "eq",
          "value": "pro"
        }
      ]
    }
  }'
put/segments/{id}

Change a segment

Scope: segments:writeoperationId: updateSegment

Changes the name and/or the filters (with the same rule check). Neither sent: 400 nothing_to_update.

Path parameters

  • idstring (uuid)required

    The segment's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

Request body · application/json

  • namestring
  • filtersFilters

    The segment filter format, shared by segments, inline send filters / excludeFilters, schedules and the audience list.

  • filters.matchstring

    all = every rule must match (default), any = at least one.

    One of: all, any. Default "all".

  • filters.rulesarray of Rule

    The rules.

    Its fields: see Send a notification.

Responses

  • 200The segment now.
    Response fields
  • 400Bad request

    invalid_jsonnothing_to_updatesegment_needs_a_rule

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

  • 413Too large

    payload_too_large

Example

cURL
curl -X PUT https://relaybell.com/api/v1/segments/ID \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "name": "Indian Pro users (all devices)"
  }'
delete/segments/{id}

Delete a segment

Scope: segments:writeoperationId: deleteSegment

While an enabled RSS feed, an enabled recurring schedule, or a scheduled or queued send uses it (to include or to exclude), it answers 409 segment_in_use with how many of each (feeds, schedules, pending). An unknown id answers { ok: true }.

Path parameters

  • idstring (uuid)required

    The segment id (not a UUID: 404 not_found).

Responses

  • 200Done.
    Response fields
    • oktruerequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

  • 409Conflict

    segment_in_use

Example

cURL
curl -X DELETE https://relaybell.com/api/v1/segments/ID \
  -H "Authorization: Bearer rb_live_xxxxx"
post/segments/preview

Count who matches

Any API keyoperationId: previewSegment

How many subscribed browsers match the filters right now (a person on two devices counts twice), optionally within a topic (id or slug; an unknown one counts nobody). Works with any key.

Request body · application/json · optional

  • filtersFilters

    The segment filter format, shared by segments, inline send filters / excludeFilters, schedules and the audience list.

  • filters.matchstring

    all = every rule must match (default), any = at least one.

    One of: all, any. Default "all".

  • filters.rulesarray of Rule

    The rules.

    Its fields: see Send a notification.

  • topicIdstring

    A topic id or slug.

Responses

  • 200The count.
    Response fields
    • countintegerrequired
  • 400Bad request

    invalid_json

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

  • 413Too large

    payload_too_large

Example

curl -X POST https://relaybell.com/api/v1/segments/preview \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "filters": {
      "match": "all",
      "rules": [
        {
          "field": "country",
          "op": "in",
          "value": [
            "IN"
          ]
        },
        {
          "field": "tag:plan",
          "op": "eq",
          "value": "pro"
        }
      ]
    }
  }'
get/audience/segments

List segments with their size

Scope: subscribers:readoperationId: listSegmentsWithCounts

Your saved segments with how many subscribed devices each matches now (the same number as /segments/preview; counted for the first 50, later ones are null), when a send last went to it, and what still uses it.

Responses

  • 200The segments.
    Response fields
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

Example

cURL
curl "https://relaybell.com/api/v1/audience/segments" \
  -H "Authorization: Bearer rb_live_xxxxx"

Topics

Preference-center categories subscribers opt in to.

get/topics

List topics

Any API keyoperationId: listTopics

Preference-center categories, oldest first, with how many subscribers opted in to each. Subscribers opt in with RelayBell.setTopics(["slug"]) or showPreferences(); target a send with topicId (id or slug).

Responses

  • 200The topics.
    Response fields
    • topicsarray of Topicrequired
    • topics[].idstring (uuid)required
    • topics[].namestringrequired
    • topics[].slugstringrequired

      Subscribers opt in by slug; it never changes.

    • topics[].created_atstring (date-time)required
    • topics[].subscribersintegerrequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

curl "https://relaybell.com/api/v1/topics" \
  -H "Authorization: Bearer rb_live_xxxxx"
post/topics

Create a topic

Scope: settings:writeoperationId: createTopic

The slug is made from the name when left out. Posting an existing slug renames that topic instead of making a second one.

Request body · application/json

  • namestringrequired

    Required (400 name_required).

  • slugstring

    Letters, digits and dashes.

Responses

  • 201Created (or renamed).
    Response fields
  • 400Bad request

    invalid_jsonname_required

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    payload_too_large

Example

curl -X POST https://relaybell.com/api/v1/topics \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "name": "Deals",
    "slug": "deals"
  }'
put/topics/{id}

Rename a topic

Scope: settings:writeoperationId: renameTopic

Only the name changes: the slug never does, since subscribers opt in by slug.

Path parameters

  • idstring (uuid)required

    The topic's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

Request body · application/json

  • namestringrequired

    Required (400 name_required).

Responses

  • 200The topic now.
    Response fields
  • 400Bad request

    invalid_jsonname_required

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

  • 413Too large

    payload_too_large

Example

cURL
curl -X PUT https://relaybell.com/api/v1/topics/ID \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "name": "Deals & offers"
  }'
delete/topics/{id}

Delete a topic

Scope: settings:writeoperationId: deleteTopic

Its opt-ins go with it. An unknown id answers { ok: true }.

Path parameters

  • idstring (uuid)required

    The topic id (not a UUID: 404 not_found).

Responses

  • 200Done.
    Response fields
    • oktruerequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

Example

cURL
curl -X DELETE https://relaybell.com/api/v1/topics/ID \
  -H "Authorization: Bearer rb_live_xxxxx"

Subscribers

Export, import, profiles, GDPR access and erasure, and the filterable subscriber list.

get/subscribers/export

Export subscribers as CSV

Scope: subscribers:readoperationId: exportSubscribers

Newest first, up to 100,000. Columns: subscriber_id, external_id, country, language, timezone, status, consent_at, devices (separated by |), tags (key=value;…), created_at.

Responses

  • 200The CSV file (attachment; filename="relaybell-subscribers.csv").text/csv
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

Example

curl "https://relaybell.com/api/v1/subscribers/export" \
  -H "Authorization: Bearer rb_live_xxxxx"
get/subscribers/recent

List the newest subscribers

Scope: subscribers:readoperationId: listRecentSubscribers

The newest subscribers first.

Query parameters

  • limitinteger

    Items per page, 1–100 (default 25).

    Default 25.

Responses

  • 200The subscribers.
    Response fields
    • subscribersarray of SubscriberRowrequired
    • subscribers[].idstring (uuid)required
    • subscribers[].external_idstring or null
    • subscribers[].countrystring or null
    • subscribers[].languagestring or null
    • subscribers[].timezonestring or null
    • subscribers[].created_atstring (date-time)required
    • subscribers[].unsubscribed_atstring (date-time) or null
    • subscribers[].devicestring or null

      The most recently seen device type.

    • subscribers[].last_seen_atstring (date-time) or null
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

Example

cURL
curl "https://relaybell.com/api/v1/subscribers/recent" \
  -H "Authorization: Bearer rb_live_xxxxx"
post/subscribers/import

Import or enrich subscribers

Scope: subscribers:writeoperationId: importSubscribers

Creates or enriches subscribers by external id with tags and topics (by slug), up to 10,000 rows; rows without an external id are skipped. Push subscriptions themselves can't be imported: each browser must opt in on your site, and these attributes apply once it does.

Request body · application/json

  • subscribersarray of objectrequired

    At least one (400 no_subscribers).

    Range: at most 10000.

  • subscribers[].externalIdstringrequired

    Also accepted as external_id.

  • subscribers[].tagsmap of string or number or boolean or null
  • subscribers[].topicsarray of string

    Topic slugs.

Responses

  • 200Processed.
    Response fields
    • oktruerequired
    • processedintegerrequired

      Rows with an external id.

    • notestring
  • 400Bad request

    invalid_jsonno_subscribers

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    payload_too_large

Example

cURL
curl -X POST https://relaybell.com/api/v1/subscribers/import \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "subscribers": [
      {
        "externalId": "user_123",
        "tags": {
          "plan": "pro",
          "name": "Jane"
        },
        "topics": [
          "deals"
        ]
      }
    ]
  }'
get/subscribers/{externalId}/data

Export one subscriber's data (GDPR access)

Scope: subscribers:readoperationId: getSubscriberData

Everything held about one subscriber (right of access): the stored row, tags, devices, up to 1,000 delivery events and 1,000 behavioral events (also those your server logged under their external id), and product interests.

Path parameters

  • externalIdstringrequired

    Your external id for the person, or relaybell's subscriber id. Unknown: 404 not_found.

Responses

  • 200The data.
    Response fields
    • subscriberobjectrequired

      The stored subscriber row.

    • tagsarray of objectrequired
    • tags[].keystring
    • tags[].valuestring or null
    • endpointsarray of objectrequired
    • endpoints[].idstring (uuid)
    • endpoints[].typestring
    • endpoints[].devicestring or null
    • endpoints[].user_agentstring or null
    • endpoints[].created_atstring (date-time)
    • endpoints[].last_seen_atstring (date-time) or null
    • deliveryEventsarray of objectrequired
    • deliveryEvents[].typestring
    • deliveryEvents[].notification_idstring (uuid) or null
    • deliveryEvents[].devicestring or null
    • deliveryEvents[].created_atstring (date-time)
    • behavioralEventsarray of objectrequired
    • behavioralEvents[].namestring
    • behavioralEvents[].propertiesobject
    • behavioralEvents[].created_atstring (date-time)
    • productInterestsarray of objectrequired
    • productInterests[].product_idstring
    • productInterests[].kindstring
    • productInterests[].created_atstring (date-time)
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

Example

cURL
curl "https://relaybell.com/api/v1/subscribers/EXTERNAL_ID/data" \
  -H "Authorization: Bearer rb_live_xxxxx"
get/subscribers/{externalId}/profile

Get a subscriber's profile

Scope: subscribers:readoperationId: getSubscriberProfile

One person: attributes, tags, devices, topics, their last 50 deliveries and clicks (with titles), their last 50 events, and lifetime delivered / clicked counts.

Path parameters

  • externalIdstringrequired

    Your external id for the person, or relaybell's subscriber id. Unknown: 404 not_found.

Responses

  • 200The profile.
    Response fields
    • subscriberobjectrequired
    • subscriber.idstring (uuid)required
    • subscriber.externalIdstring or null
    • subscriber.countrystring or null
    • subscriber.languagestring or null
    • subscriber.timezonestring or null
    • subscriber.createdAtstring (date-time)
    • subscriber.unsubscribedAtstring (date-time) or null
    • subscriber.consentAtstring (date-time) or null
    • tagsarray of objectrequired
    • tags[].keystring
    • tags[].valuestring or null
    • endpointsarray of objectrequired
    • endpoints[].idstring (uuid)
    • endpoints[].devicestring or null
    • endpoints[].user_agentstring or null
    • endpoints[].created_atstring (date-time)
    • endpoints[].last_seen_atstring (date-time) or null
    • topicsarray of objectrequired
    • topics[].namestring
    • topics[].slugstring
    • sendsarray of objectrequired
    • sends[].typestring
    • sends[].notification_idstring (uuid) or null
    • sends[].created_atstring (date-time)
    • sends[].titlestring or null
    • eventsarray of objectrequired
    • events[].namestring
    • events[].propertiesobject
    • events[].created_atstring (date-time)
    • statsobjectrequired
    • stats.deliveredintegerrequired
    • stats.clickedintegerrequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

Example

curl "https://relaybell.com/api/v1/subscribers/user_123/profile" \
  -H "Authorization: Bearer rb_live_xxxxx"
delete/subscribers/{externalId}

Erase a subscriber (GDPR erasure)

Scope: subscribers:writeoperationId: eraseSubscriber

Permanently deletes everything about one subscriber: devices, tags, topics, journeys and their delivery and behavioral events (also those logged under their external id).

Path parameters

  • externalIdstringrequired

    Your external id for the person, or relaybell's subscriber id. Unknown: 404 not_found.

Responses

  • 200Erased.
    Response fields
    • oktruerequired
    • erasedtruerequired
    • subscriberIdstring (uuid)required
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

Example

cURL
curl -X DELETE https://relaybell.com/api/v1/subscribers/EXTERNAL_ID \
  -H "Authorization: Bearer rb_live_xxxxx"
get/audience/subscribers

List and filter subscribers

Scope: subscribers:readoperationId: listAudience

The dashboard's subscriber list: newest first, paged by cursor, every filter narrowing the list further (they combine with AND). List filters accept a comma-separated value or repeated parameters.

Query parameters

  • limitinteger

    Items per page, 1–100 (default 25).

    Default 25.

  • cursorstring

    The nextCursor from the previous page (send the same filters again). Anything else answers 400 invalid_cursor.

  • qstring

    A subscriber id (exact) or part of an external id.

  • statusstring

    Default all.

    One of: all, subscribed, unsubscribed, no_device. Default "all".

  • countrystring

    2-letter codes, comma-separated.

  • languagestring

    "en" matches en, en-US, en-GB…; "en-US" matches only en-US.

  • devicestring

    desktop, android, ios, mobile, other (400 invalid_device).

  • topicstring

    Topic ids or slugs (up to 5); an unknown one matches nobody.

  • tagstring

    key (has the label) or key:value / key=value (with that value); up to 10.

  • joinedFromstring (date)

    Joined on or after this day (YYYY-MM-DD, in tz).

  • joinedTostring (date)

    Joined on or before this day.

  • seenFromstring (date)

    Last seen on or after this day.

  • seenTostring (date)

    Last seen on or before this day.

  • tzstring

    IANA time zone for the days (default UTC).

  • filtersstring

    Segment rules as JSON, e.g. {"match":"all","rules":[…]} (at most 20 rules; 400 invalid_filters).

  • countstring

    "1" or "true" adds total, the number of matches.

    One of: 1, true.

Responses

  • 200A page of subscribers.
    Response fields
    • itemsarray of AudienceSubscriberrequired
    • items[].idstring (uuid)required
    • items[].externalIdstring or null
    • items[].countrystring or null
    • items[].languagestring or null
    • items[].timezonestring or null
    • items[].statusstringrequired

      One of: subscribed, unsubscribed, no_device.

    • items[].devicestring or null
    • items[].devicesarray of string
    • items[].topicsarray of object
    • items[].tagsarray of object

      Up to 20; rb_ labels are left out.

    • items[].createdAtstring (date-time)required
    • items[].lastSeenAtstring (date-time) or null
    • items[].unsubscribedAtstring (date-time) or null
    • nextCursorstring or nullrequired
    • totalinteger

      Only with count=1.

  • 400Bad request

    invalid_statusinvalid_deviceinvalid_rangeinvalid_tzinvalid_filtersinvalid_cursor

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

Example

cURL
curl "https://relaybell.com/api/v1/audience/subscribers" \
  -H "Authorization: Bearer rb_live_xxxxx"
get/audience/subscribers/facets

Get filter options

Scope: subscribers:readoperationId: getAudienceFacets

The values this website's subscribers actually have, with counts: countries, languages, devices, labels (rb_ ones left out; up to 20 values each) and topics.

Responses

  • 200The options.
    Response fields
    • countriesarray of objectrequired
    • countries[].valuestring
    • countries[].countinteger
    • languagesarray of objectrequired
    • languages[].valuestring
    • languages[].countinteger
    • devicesarray of objectrequired
    • devices[].valuestring
    • devices[].countinteger
    • tagsarray of objectrequired
    • tags[].keystring
    • tags[].countinteger
    • tags[].valuesarray of string
    • topicsarray of objectrequired
    • topics[].idstring (uuid)
    • topics[].namestring
    • topics[].slugstring
    • topics[].countinteger
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

Example

cURL
curl "https://relaybell.com/api/v1/audience/subscribers/facets" \
  -H "Authorization: Bearer rb_live_xxxxx"
post/audience/subscribers/bulk-tag

Label subscribers in bulk

Scope: subscribers:writeoperationId: bulkTagSubscribers

Adds, changes or removes labels (tags) on up to 1,000 subscribers. At most 20 labels at a time; names are 1–64 characters, values up to 256; names starting with rb_ are reserved. Ids that aren't this website's are skipped.

Request body · application/json

  • idsarray of string (uuid)required

    Subscriber ids (400 ids_required, too_many_ids, invalid_id).

    Range: at most 1000.

  • setmap of string or null

    Labels to add or change: { key: value }.

  • removearray of string

    Label names to remove.

Responses

  • 200Done.
    Response fields
    • oktruerequired
    • updatedintegerrequired
  • 400Bad request

    invalid_jsonnothing_to_changetoo_many_labelsinvalid_labelreserved_labelids_requiredtoo_many_idsinvalid_id

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    payload_too_large

Example

cURL
curl -X POST https://relaybell.com/api/v1/audience/subscribers/bulk-tag \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "ids": [
      "e1c9b7a5-3d2f-4e8b-9a6c-0f4d2b8e1a73"
    ],
    "set": {
      "plan": "pro"
    },
    "remove": [
      "trial"
    ]
  }'
post/audience/subscribers/bulk-delete

Erase subscribers in bulk

Scope: subscribers:writeoperationId: bulkDeleteSubscribers

The same full erasure as DELETE /subscribers/{externalId}, for up to 1,000 subscribers. Ids that aren't this website's are skipped.

Request body · application/json

  • idsarray of string (uuid)required

    Range: at most 1000.

Responses

  • 200Done.
    Response fields
    • oktruerequired
    • deletedintegerrequired
  • 400Bad request

    invalid_jsonids_requiredtoo_many_idsinvalid_id

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    payload_too_large

Example

cURL
curl -X POST https://relaybell.com/api/v1/audience/subscribers/bulk-delete \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "ids": [
      "e1c9b7a5-3d2f-4e8b-9a6c-0f4d2b8e1a73"
    ]
  }'

Webhooks

Signed POSTs to your server when something happens, with a 30-day delivery log, resend and test events. See Verify a webhook signature.

get/webhooks

List webhooks

Any API keyoperationId: listWebhooks

Your webhooks, newest first, with each one's last attempt and health, plus the event names you can subscribe to. Never the secrets.

Responses

  • 200The webhooks.
    Response fields
    • webhooksarray of Webhookrequired
    • webhooks[].idstring (uuid)required
    • webhooks[].urlstringrequired
    • webhooks[].eventsarray of stringrequired

      Empty = every event.

    • webhooks[].enabledbooleanrequired
    • webhooks[].last_statusinteger or null

      HTTP status of the last attempt (0 when no answer came back).

    • webhooks[].last_fired_atstring (date-time) or null
    • webhooks[].created_atstring (date-time)required
    • webhooks[].consecutive_failuresintegerrequired

      Deliveries failed in a row since the last success.

    • webhooks[].failing_sincestring (date-time) or null

      First failure since the last success.

    • webhooks[].disabled_reasonstring or null

      Set when relaybell turned it off.

      One of: failed_50_in_a_row, failing_for_3_days.

    • webhooks[].disabled_atstring (date-time) or null
    • eventsarray of stringrequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

curl "https://relaybell.com/api/v1/webhooks" \
  -H "Authorization: Bearer rb_live_xxxxx"
post/webhooks

Create a webhook

Scope: webhooks:writeoperationId: createWebhook

The url must start with http:// or https:// (400 invalid_url). One pointing at a private or internal address is saved, but every delivery to it is refused. An empty events list means every event; unknown names are dropped. The secret (whsec_…) is shown only in this answer — store it to verify signatures.

Request body · application/json

  • urlstringrequired

    Where to POST events.

  • eventsarray of string

    Empty or left out = every event.

Responses

  • 201Created. The only time you see the secret.
    Response fields
    • webhookobjectrequired
    • webhook.idstring (uuid)required
    • webhook.urlstringrequired
    • webhook.eventsarray of stringrequired
    • webhook.secretstringrequired
    • webhook.enabledbooleanrequired
    • webhook.created_atstring (date-time)required
  • 400Bad request

    invalid_jsoninvalid_url

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    payload_too_large

Example

curl -X POST https://relaybell.com/api/v1/webhooks \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.example/webhooks/relaybell",
    "events": [
      "notification.clicked"
    ]
  }'
put/webhooks/{id}

Change a webhook

Scope: webhooks:writeoperationId: updateWebhook

Changes url, events and/or enabled. Turning a webhook that was off back on (enabled: true) clears its failure count and the reason relaybell turned it off. Nothing sent: 400 nothing_to_update.

Path parameters

  • idstring (uuid)required

    The webhook's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

Request body · application/json

  • urlstring

    Must start with http:// or https://.

  • eventsarray of string
  • enabledboolean

Responses

  • 200The webhook now.
    Response fields
  • 400Bad request

    invalid_jsoninvalid_urlnothing_to_update

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

  • 413Too large

    payload_too_large

Example

curl -X PUT https://relaybell.com/api/v1/webhooks/c3e5a7b9-2d4f-4a6c-8e0b-1d3f5a7c9e2b \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "enabled": true
  }'
delete/webhooks/{id}

Delete a webhook

Scope: webhooks:writeoperationId: deleteWebhook

Removes the webhook and its delivery log. An unknown id also answers { ok: true }.

Path parameters

  • idstring (uuid)required

    The webhook id.

Responses

  • 200Done.
    Response fields
    • oktruerequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

Example

cURL
curl -X DELETE https://relaybell.com/api/v1/webhooks/ID \
  -H "Authorization: Bearer rb_live_xxxxx"
get/webhooks/{id}/deliveries

List a webhook's deliveries

Scope: webhooks:writeoperationId: listWebhookDeliveries

Every delivery and the result of its last attempt, kept for 30 days: newest first, 25 a page (limit up to 100).

Path parameters

  • idstring (uuid)required

    The webhook's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

Query parameters

  • limitinteger

    Items per page, 1–100 (default 25).

    Default 25.

  • cursorstring

    The nextCursor from the previous page (send the same filters again). Anything else answers 400 invalid_cursor.

Responses

  • 200A page of deliveries.
    Response fields
    • deliveriesarray of WebhookDeliveryrequired
    • deliveries[].idstring (uuid)required

      The delivery id, also sent as X-Relaybell-Delivery (the same on every retry and resend).

    • deliveries[].eventstringrequired
    • deliveries[].statusstringrequired

      pending (waiting for its first attempt, or a resend / test waiting to run), retrying, succeeded (the last attempt got a 2xx) or failed.

      One of: pending, retrying, succeeded, failed.

    • deliveries[].attemptsintegerrequired

      Every attempt, resends included.

    • deliveries[].last_status_codeinteger or null

      null when no answer came back (error says why).

    • deliveries[].duration_msinteger or null
    • deliveries[].errorstring or null

      Why the last attempt failed, in plain words.

    • deliveries[].created_atstring (date-time)required
    • deliveries[].last_attempt_atstring (date-time) or null
    • deliveries[].next_attempt_atstring (date-time) or null

      When the next retry runs.

    • deliveries[].delivered_atstring (date-time) or null
    • nextCursorstring or nullrequired
  • 400Bad request

    invalid_cursor

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

Example

curl "https://relaybell.com/api/v1/webhooks/c3e5a7b9-2d4f-4a6c-8e0b-1d3f5a7c9e2b/deliveries?limit=25" \
  -H "Authorization: Bearer rb_live_xxxxx"
get/webhooks/{id}/deliveries/{deliveryId}

Get one delivery

Scope: webhooks:writeoperationId: getWebhookDelivery

The delivery with the exact body that was signed and sent, the last attempt's headers (signatures included, never your secret) and the start of your server's answer.

Path parameters

  • idstring (uuid)required

    The webhook's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

  • deliveryIdstring (uuid)required

    The delivery id (X-Relaybell-Delivery).

Responses

  • 200The delivery.
    Response fields
    • deliveryWebhookDeliveryDetailrequired
    • delivery.idstring (uuid)required

      The delivery id, also sent as X-Relaybell-Delivery (the same on every retry and resend).

    • delivery.eventstringrequired
    • delivery.statusstringrequired

      pending (waiting for its first attempt, or a resend / test waiting to run), retrying, succeeded (the last attempt got a 2xx) or failed.

      One of: pending, retrying, succeeded, failed.

    • delivery.attemptsintegerrequired

      Every attempt, resends included.

    • delivery.last_status_codeinteger or null

      null when no answer came back (error says why).

    • delivery.duration_msinteger or null
    • delivery.errorstring or null

      Why the last attempt failed, in plain words.

    • delivery.created_atstring (date-time)required
    • delivery.last_attempt_atstring (date-time) or null
    • delivery.next_attempt_atstring (date-time) or null

      When the next retry runs.

    • delivery.delivered_atstring (date-time) or null
    • delivery.webhook_idstring (uuid)
    • delivery.request_bodystring

      Exactly what was signed and sent, up to 64 KB.

    • delivery.request_truncatedboolean

      true when the body was longer than 64 KB.

    • delivery.request_headersobject or null

      The last attempt's headers (content-type, user-agent, x-relaybell-event, x-relaybell-delivery and both signatures). Never your secret.

    • delivery.response_bodystring or null

      The first 2 KB of your server's last answer.

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

Example

curl "https://relaybell.com/api/v1/webhooks/c3e5a7b9-2d4f-4a6c-8e0b-1d3f5a7c9e2b/deliveries/f0e1d2c3-b4a5-4697-8869-5a4b3c2d1e0f" \
  -H "Authorization: Bearer rb_live_xxxxx"
post/webhooks/{id}/deliveries/{deliveryId}/resend

Resend a delivery

Scope: webhooks:writeoperationId: resendWebhookDelivery

One more attempt now, with the same delivery id and body and a fresh timestamp and signature — no retries after it. A waiting retry is replaced. Resends and test events together: 30 a minute per website.

Path parameters

  • idstring (uuid)required

    The webhook's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

  • deliveryIdstring (uuid)required

    The delivery id.

Responses

  • 202Queued.
    Response fields
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

  • 409Conflict

    delivery_in_progressbody_too_large

  • 429Too many requests

    rate_limited

Example

curl -X POST https://relaybell.com/api/v1/webhooks/c3e5a7b9-2d4f-4a6c-8e0b-1d3f5a7c9e2b/deliveries/f0e1d2c3-b4a5-4697-8869-5a4b3c2d1e0f/resend \
  -H "Authorization: Bearer rb_live_xxxxx"
post/webhooks/{id}/test

Send a test event

Scope: webhooks:writeoperationId: sendWebhookTest

Sends a webhook.test event ({ webhookId, message }) to this webhook once, no retries — even when the webhook is turned off or subscribes to other events. Follow its result with the delivery routes. Shares the 30-a-minute limit with resends.

Path parameters

  • idstring (uuid)required

    The webhook's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

Responses

  • 202Queued.
    Response fields
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

  • 429Too many requests

    rate_limited

Example

curl -X POST https://relaybell.com/api/v1/webhooks/c3e5a7b9-2d4f-4a6c-8e0b-1d3f5a7c9e2b/test \
  -H "Authorization: Bearer rb_live_xxxxx"

Automations

One push per event: cart reminders, back-in-stock and price-drop alerts, welcome-back flows.

get/automations

List automations

Any API keyoperationId: listAutomations

Newest first, each with sent (all-time deliveries) and its last-30-day numbers.

Responses

  • 200The automations.
    Response fields
    • automationsarray of AutomationWithStatsrequired
    • automations[].idstring (uuid)required
    • automations[].project_idstring (uuid)
    • automations[].namestringrequired
    • automations[].enabledbooleanrequired
    • automations[].eventstringrequired

      The event name that starts it.

    • automations[].conditionsarray of objectrequired
    • automations[].actionobjectrequired
    • automations[].delay_secondsintegerrequired
    • automations[].cancel_onstring or null
    • automations[].once_per_subscriberboolean
    • automations[].created_atstring (date-time)required
    • automations[].sentintegerrequired

      All-time deliveries.

    • automations[].stats30dStats30drequired

      The last 30 days, from the notifications it produced.

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/automations" \
  -H "Authorization: Bearer rb_live_xxxxx"
post/automations

Create an automation

Scope: settings:writeoperationId: createAutomation

One push to a person when they do something: an event arrives, the optional delay passes, the message goes out. With back_in_stock or price_drop as the event it powers product alerts. Automation pushes follow your quiet hours and frequency cap.

Request body · application/json

  • eventstringrequired

    The event name that starts it (required: 400 event_required). Up to 120 characters.

  • actionobject

    The push. Merge tags take values from the event's properties: {product}, {url}, {price|} …

  • action.titlestring

    Required (400 action_title_required on create).

  • action.bodystring
  • action.urlstring
  • action.iconstring
  • namestring

    Up to 120 characters (default "Automation").

  • delaySecondsinteger

    Wait before sending (default 0). A newer event from the same person replaces their pending one.

  • cancelOnstring

    An event name that cancels the person's pending push, e.g. purchase.

  • conditionsarray of object

    Checked against the event's properties; all must match. On PUT, left out = kept as they are.

  • conditions[].fieldstringrequired
  • conditions[].opstring

    One of: eq, neq, contains, exists, gt, gte, lt, lte. Default "eq".

  • conditions[].valueany
  • oncePerSubscriberboolean

    At most once per person, ever.

  • enabledboolean

    Default true. On PUT, it goes back to true unless you send false.

Responses

  • 201Created.
    Response fields
  • 400Bad request

    invalid_jsonevent_requiredaction_title_required

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    payload_too_large

Example

cURL
curl -X POST https://relaybell.com/api/v1/automations \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "name": "Cart abandon",
    "event": "cart.updated",
    "delaySeconds": 3600,
    "cancelOn": "purchase",
    "conditions": [
      {
        "field": "cartTotal",
        "op": "gte",
        "value": 20
      }
    ],
    "action": {
      "title": "You left {product|something} in your cart 🛒",
      "body": "Complete your order before it's gone.",
      "url": "{url}"
    }
  }'
get/automations/{id}

Get an automation

Any API keyoperationId: getAutomation

One automation with its all-time deliveries and last-30-day numbers.

Path parameters

  • idstring (uuid)required

    The automation's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

Responses

  • 200The automation.
    Response fields
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

  • 404Not found

    not_found

Example

cURL
curl "https://relaybell.com/api/v1/automations/ID" \
  -H "Authorization: Bearer rb_live_xxxxx"
put/automations/{id}

Replace an automation

Scope: settings:writeoperationId: updateAutomation

Replaces the automation with the body you send — enabled goes back to true unless you send false — except conditions, which are kept when the body has no conditions key.

Path parameters

  • idstring (uuid)required

    The automation's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

Request body · application/json

The same fields as Create an automation.

Responses

  • 200The automation now.
    Response fields
  • 400Bad request

    invalid_jsonevent_required

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

  • 413Too large

    payload_too_large

Example

cURL
curl -X PUT https://relaybell.com/api/v1/automations/ID \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "name": "Cart abandon",
    "event": "cart.updated",
    "delaySeconds": 7200,
    "cancelOn": "purchase",
    "action": {
      "title": "Still thinking it over?",
      "url": "{url}"
    }
  }'
delete/automations/{id}

Delete an automation

Scope: settings:writeoperationId: deleteAutomation

An unknown id answers { ok: true }.

Path parameters

  • idstring (uuid)required

    The automation id (not a UUID: 404 not_found).

Responses

  • 200Done.
    Response fields
    • oktruerequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

Example

cURL
curl -X DELETE https://relaybell.com/api/v1/automations/ID \
  -H "Authorization: Bearer rb_live_xxxxx"

Journeys

Multi-step journeys (drip sequences) that start on subscribe or on an event.

get/sequences

List journeys

Any API keyoperationId: listJourneys

Multi-step journeys (drip sequences), newest first, with their steps, enrollments and last-30-day numbers.

Responses

  • 200The journeys.
    Response fields
    • sequencesarray of JourneyWithStatsrequired
    • sequences[].idstring (uuid)required
    • sequences[].project_idstring (uuid)
    • sequences[].namestringrequired
    • sequences[].enabledbooleanrequired
    • sequences[].trigger_typestringrequired

      One of: subscribe, event.

    • sequences[].trigger_eventstring or null
    • sequences[].cancel_onstring or null
    • sequences[].created_atstring (date-time)required
    • sequences[].stepsarray of JourneySteprequired
    • sequences[].enrollmentsobjectrequired
    • sequences[].stats30dStats30drequired

      The last 30 days, from the notifications it produced.

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/sequences" \
  -H "Authorization: Bearer rb_live_xxxxx"
post/sequences

Create a journey

Scope: settings:writeoperationId: createJourney

A subscriber enters when they subscribe (trigger: "subscribe", the default) or send an event (trigger: "event" with triggerEvent), then moves through the steps in order; cancelOn takes them out. Each subscriber enters a journey once, ever. A branch step continues only for people who match its condition.

Request body · application/json

  • namestring

    Up to 120 characters (default "Sequence").

  • triggerstring

    "subscribe" (default) or "event".

    One of: subscribe, event.

  • triggerEventstring

    Required with "event" (400 trigger_event_required).

  • cancelOnstring

    An event that takes the person out of the journey, e.g. purchase.

  • enabledboolean

    Default true.

  • stepsarray of object

    At least one (400 at_least_one_step_required). On PUT, steps change only when you send them.

  • steps[].idstring (uuid)

    An existing step's id, to keep it (and its numbers) when editing.

  • steps[].typestring

    "message" (default) or "branch".

    One of: message, branch.

  • steps[].delaySecondsinteger

    The wait after the previous step (for the first step, after entering).

  • steps[].titlestring

    A message step without a title is dropped.

  • steps[].bodystring
  • steps[].urlstring
  • steps[].iconstring
  • steps[].conditionobject

    Branch: the person goes on only if they match (segment rule format).

  • condition.fieldstringrequired
  • condition.opstring
  • condition.valuestring

Responses

  • 201Created (the steps are in GET /sequences/{id}).
    Response fields
  • 400Bad request

    invalid_jsontrigger_event_requiredat_least_one_step_required

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    payload_too_large

Example

cURL
curl -X POST https://relaybell.com/api/v1/sequences \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "name": "Welcome series",
    "trigger": "subscribe",
    "cancelOn": "purchase",
    "steps": [
      {
        "delaySeconds": 0,
        "title": "Welcome 👋",
        "url": "https://shop.example"
      },
      {
        "delaySeconds": 86400,
        "title": "Here's how to start"
      },
      {
        "type": "branch",
        "condition": {
          "field": "tag:plan",
          "op": "eq",
          "value": "free"
        }
      },
      {
        "delaySeconds": 604800,
        "title": "A little something for you"
      }
    ]
  }'
get/sequences/{id}

Get a journey

Any API keyoperationId: getJourney

The steps with their ids and 30-day numbers, and enrollments { active, completed, canceled }.

Path parameters

  • idstring (uuid)required

    The journey's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

Responses

  • 200The journey.
    Response fields
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

  • 404Not found

    not_found

Example

cURL
curl "https://relaybell.com/api/v1/sequences/ID" \
  -H "Authorization: Bearer rb_live_xxxxx"
put/sequences/{id}

Change a journey

Scope: settings:writeoperationId: updateJourney

Always replaces name, enabled, trigger and cancelOn (send them all); steps change only when you send them. While nobody is in the journey you can reorder, add and remove steps (send a step's id to keep it and its numbers). While someone is in it, send every existing step back with its id, in the same order and type, and only add new steps at the end — anything else answers 409 structural_change_requires_no_active_enrollments.

Path parameters

  • idstring (uuid)required

    The journey's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

Request body · application/json

The same fields as Create a journey.

Responses

  • 200The journey now (without steps).
    Response fields
  • 400Bad request

    invalid_jsontrigger_event_requiredat_least_one_step_required

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

  • 409Conflict

    structural_change_requires_no_active_enrollments

  • 413Too large

    payload_too_large

Example

cURL
curl -X PUT https://relaybell.com/api/v1/sequences/ID \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "name": "Welcome series",
    "trigger": "subscribe",
    "cancelOn": "purchase",
    "enabled": false
  }'
delete/sequences/{id}

Delete a journey

Scope: settings:writeoperationId: deleteJourney

An unknown id answers { ok: true }.

Path parameters

  • idstring (uuid)required

    The journey id (not a UUID: 404 not_found).

Responses

  • 200Done.
    Response fields
    • oktruerequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

Example

cURL
curl -X DELETE https://relaybell.com/api/v1/sequences/ID \
  -H "Authorization: Bearer rb_live_xxxxx"

RSS feeds

New posts from a feed, sent automatically.

get/feeds

List RSS feeds

Any API keyoperationId: listFeeds

RSS / Atom feeds whose new posts are sent automatically (checked about every 15 minutes).

Responses

  • 200The feeds.
    Response fields
    • feedsarray of Feedrequired
    • feeds[].idstring (uuid)required
    • feeds[].urlstringrequired
    • feeds[].enabledbooleanrequired
    • feeds[].segment_idstring (uuid) or nullrequired
    • feeds[].last_guidstring or null
    • feeds[].last_checked_atstring (date-time) or null
    • feeds[].created_atstring (date-time)
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/feeds" \
  -H "Authorization: Bearer rb_live_xxxxx"
post/feeds

Add an RSS feed

Scope: settings:writeoperationId: createFeed

New posts in the feed are sent to everyone, or to a segment.

Request body · application/json

  • urlstringrequired

    http(s) feed URL (400 invalid_url).

  • segmentIdstring (uuid)

    A segment of this website (400 invalid_segment).

Responses

  • 201Added.
    Response fields
  • 400Bad request

    invalid_jsoninvalid_urlinvalid_segment

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    payload_too_large

Example

cURL
curl -X POST https://relaybell.com/api/v1/feeds \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "url": "https://blog.example/feed.xml"
  }'
put/feeds/{id}

Change an RSS feed

Scope: settings:writeoperationId: updateFeed

Turns it on or off, or changes its segment (null = everyone). Nothing sent: 400 nothing_to_update.

Path parameters

  • idstring (uuid)required

    The feed's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

Request body · application/json

  • enabledboolean
  • segmentIdstring (uuid) or null

Responses

  • 200The feed now.
    Response fields
    • feedobjectrequired
    • feed.idstring (uuid)required
    • feed.urlstringrequired
    • feed.enabledbooleanrequired
    • feed.segment_idstring (uuid) or nullrequired
  • 400Bad request

    invalid_jsonnothing_to_updateinvalid_segment

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

  • 413Too large

    payload_too_large

Example

cURL
curl -X PUT https://relaybell.com/api/v1/feeds/ID \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "enabled": false
  }'
delete/feeds/{id}

Delete an RSS feed

Scope: settings:writeoperationId: deleteFeed

An unknown id answers { ok: true }.

Path parameters

  • idstring (uuid)required

    The feed id (not a UUID: 404 not_found).

Responses

  • 200Done.
    Response fields
    • oktruerequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

Example

cURL
curl -X DELETE https://relaybell.com/api/v1/feeds/ID \
  -H "Authorization: Bearer rb_live_xxxxx"

Recurring schedules

The same message every hour, day or week.

get/schedules

List recurring campaigns

Any API keyoperationId: listSchedules

Newest first, with when each runs next and last ran.

Responses

  • 200The schedules.
    Response fields
    • schedulesarray of Schedulerequired
    • schedules[].idstring (uuid)required
    • schedules[].project_idstring (uuid)
    • schedules[].namestringrequired
    • schedules[].contentobjectrequired

      { title, body, url, icon, segmentId | filters, topicId, excludeSegmentId | excludeFilters }

    • schedules[].cadencestringrequired

      One of: hourly, daily, weekly.

    • schedules[].at_timestring or null

      "HH:MM".

    • schedules[].day_of_weekinteger or null

      0–6, 0 = Sunday.

    • schedules[].timezonestringrequired
    • schedules[].enabledbooleanrequired
    • schedules[].next_run_atstring (date-time) or null
    • schedules[].last_run_atstring (date-time) or null
    • schedules[].created_atstring (date-time)
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/schedules" \
  -H "Authorization: Bearer rb_live_xxxxx"
post/schedules

Create a recurring campaign

Scope: settings:writeoperationId: createSchedule

The same message every hour, day or week, at a time in a time zone, to everyone or to the people the content targets.

Request body · application/json

  • namestring

    Up to 120 characters (default "Recurring campaign").

  • contentobjectrequired
  • content.titlestringrequired

    Required (400 content_title_required).

  • content.bodystring
  • content.urlstring
  • content.iconstring
  • content.segmentIdstring (uuid)
  • content.filtersFilters

    The segment filter format, shared by segments, inline send filters / excludeFilters, schedules and the audience list.

  • filters.matchstring

    all = every rule must match (default), any = at least one.

    One of: all, any. Default "all".

  • filters.rulesarray of Rule

    The rules.

  • content.topicIdstring

    Id or slug (400 unknown_topic).

  • content.excludeSegmentIdstring (uuid)
  • content.excludeFiltersFilters

    The segment filter format, shared by segments, inline send filters / excludeFilters, schedules and the audience list.

  • excludeFilters.matchstring

    all = every rule must match (default), any = at least one.

    One of: all, any. Default "all".

  • excludeFilters.rulesarray of Rule

    The rules.

  • cadencestring

    Default daily.

    One of: hourly, daily, weekly. Default "daily".

  • atTimestring

    "HH:MM" (default "09:00").

    Default "09:00".

  • dayOfWeekinteger

    0–6, 0 = Sunday (default 1).

    Default 1. Range: 0–6.

  • timezonestring

    An IANA name (default UTC; 400 invalid_timezone).

    Default "UTC".

  • enabledboolean

    Default true.

Responses

  • 201Created.
    Response fields
  • 400Bad request

    invalid_jsoncontent_title_requiredinvalid_timezoneunknown_topic

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    payload_too_large

Example

cURL
curl -X POST https://relaybell.com/api/v1/schedules \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "name": "Daily digest",
    "content": {
      "title": "Today's picks",
      "url": "https://shop.example/today",
      "topicId": "deals"
    },
    "cadence": "daily",
    "atTime": "09:00",
    "timezone": "Asia/Kolkata"
  }'
put/schedules/{id}

Replace a recurring campaign

Scope: settings:writeoperationId: updateSchedule

Replaces the whole schedule (fields left out take their defaults) and works out its next run again.

Path parameters

  • idstring (uuid)required

    The schedule's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

Request body · application/json

The same fields as Create a recurring campaign.

Responses

  • 200The schedule now.
    Response fields
  • 400Bad request

    invalid_jsoncontent_title_requiredinvalid_timezoneunknown_topic

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

  • 413Too large

    payload_too_large

Example

cURL
curl -X PUT https://relaybell.com/api/v1/schedules/ID \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "name": "Daily digest",
    "content": {
      "title": "Today's picks"
    },
    "cadence": "weekly",
    "dayOfWeek": 1,
    "atTime": "10:00",
    "timezone": "UTC",
    "enabled": true
  }'
delete/schedules/{id}

Delete a recurring campaign

Scope: settings:writeoperationId: deleteSchedule

An unknown id answers { ok: true }.

Path parameters

  • idstring (uuid)required

    The schedule id (not a UUID: 404 not_found).

Responses

  • 200Done.
    Response fields
    • oktruerequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

Example

cURL
curl -X DELETE https://relaybell.com/api/v1/schedules/ID \
  -H "Authorization: Bearer rb_live_xxxxx"

Templates

Saved notification copy.

get/templates

List templates

Any API keyoperationId: listTemplates

Saved notification copy, newest first.

Responses

  • 200The templates.
    Response fields
    • templatesarray of Templaterequired
    • templates[].idstring (uuid)required
    • templates[].namestringrequired
    • templates[].contentobjectrequired

      Any object, e.g. a send body.

    • templates[].created_atstring (date-time)required
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/templates" \
  -H "Authorization: Bearer rb_live_xxxxx"
post/templates

Create a template

Scope: settings:writeoperationId: createTemplate

content is any object, e.g. a send body.

Request body · application/json

  • namestringrequired

    Required (400 name_required).

  • contentobject

Responses

  • 201Created.
    Response fields
  • 400Bad request

    invalid_jsonname_required

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    payload_too_large

Example

cURL
curl -X POST https://relaybell.com/api/v1/templates \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "name": "Sale",
    "content": {
      "title": "Flash sale 🔥",
      "body": "30% off ends tonight"
    }
  }'
put/templates/{id}

Replace a template

Scope: settings:writeoperationId: updateTemplate

Replaces name and content.

Path parameters

  • idstring (uuid)required

    The template's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

Request body · application/json

  • namestringrequired

    Required (400 name_required).

  • contentobject

Responses

  • 200The template now.
    Response fields
  • 400Bad request

    invalid_jsonname_required

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

  • 413Too large

    payload_too_large

Example

cURL
curl -X PUT https://relaybell.com/api/v1/templates/ID \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "name": "Sale",
    "content": {
      "title": "Last day of the sale"
    }
  }'
delete/templates/{id}

Delete a template

Scope: settings:writeoperationId: deleteTemplate

An unknown id answers { ok: true }.

Path parameters

  • idstring (uuid)required

    The template id (not a UUID: 404 not_found).

Responses

  • 200Done.
    Response fields
    • oktruerequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

Example

cURL
curl -X DELETE https://relaybell.com/api/v1/templates/ID \
  -H "Authorization: Bearer rb_live_xxxxx"

Goals

Conversion goals and the funnel report.

get/goals

List conversion goals

Any API keyoperationId: listGoals

Oldest first.

Responses

  • 200The goals.
    Response fields
    • goalsarray of Goalrequired
    • goals[].idstring (uuid)required
    • goals[].namestringrequired
    • goals[].eventNamestringrequired
    • goals[].createdAtstring (date-time)required
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/goals" \
  -H "Authorization: Bearer rb_live_xxxxx"
post/goals

Create a goal

Scope: settings:writeoperationId: createGoal

A goal counts one of your events. Posting an existing name changes its event instead.

Request body · application/json

  • namestringrequired

    Up to 80 characters.

  • eventNamestringrequired

    Up to 120 characters.

Responses

  • 201Created (or changed).
    Response fields
  • 400Bad request

    invalid_jsonname_and_event_required

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    payload_too_large

Example

cURL
curl -X POST https://relaybell.com/api/v1/goals \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "name": "Purchases",
    "eventName": "purchase"
  }'
get/goals/stats

Goal report and funnel

Any API keyoperationId: getGoalStats

Over the last days days: unique subscribers delivered a push and who clicked, then per goal its conversions, unique converting subscribers and revenue (numeric value properties).

Query parameters

  • daysinteger

    1–365 (default 30).

    Default 30.

Responses

  • 200The report.
    Response fields
    • windowDaysintegerrequired
    • funnelobjectrequired
    • funnel.deliveredintegerrequired
    • funnel.clickedintegerrequired
    • goalsarray of objectrequired
    • goals[].idstring (uuid)
    • goals[].namestring
    • goals[].eventNamestring
    • goals[].conversionsinteger
    • goals[].subscribersinteger
    • goals[].revenuenumber
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/goals/stats" \
  -H "Authorization: Bearer rb_live_xxxxx"
put/goals/{id}

Change a goal

Scope: settings:writeoperationId: updateGoal

Changes its name and event (both required). The name of another goal: 409 name_taken.

Path parameters

  • idstring (uuid)required

    The goal's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

Request body · application/json

  • namestringrequired
  • eventNamestringrequired

Responses

  • 200The goal now.
    Response fields
  • 400Bad request

    invalid_jsonname_and_event_required

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

  • 409Conflict

    name_taken

  • 413Too large

    payload_too_large

Example

cURL
curl -X PUT https://relaybell.com/api/v1/goals/ID \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "name": "Orders",
    "eventName": "purchase"
  }'
delete/goals/{id}

Delete a goal

Scope: settings:writeoperationId: deleteGoal

An unknown id answers { ok: true }.

Path parameters

  • idstring (uuid)required

    The goal id (not a UUID: 404 not_found).

Responses

  • 200Done.
    Response fields
    • oktruerequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

Example

cURL
curl -X DELETE https://relaybell.com/api/v1/goals/ID \
  -H "Authorization: Bearer rb_live_xxxxx"

On-site messages

Banners and slide-ins shown on your pages.

get/onsite

List on-site messages

Any API keyoperationId: listOnsiteMessages

Banners and slide-ins the SDK shows to every visitor (the 5 newest active ones), with impressions and clicks.

Responses

  • 200The messages.
    Response fields
    • messagesarray of OnsiteMessagerequired
    • messages[].idstring (uuid)required
    • messages[].project_idstring (uuid)
    • messages[].titlestringrequired
    • messages[].bodystring or null
    • messages[].cta_textstring or null
    • messages[].cta_urlstring or null
    • messages[].stylestringrequired

      One of: slidein, banner.

    • messages[].activebooleanrequired
    • messages[].created_atstring (date-time)required
    • messages[].impressionsintegerrequired
    • messages[].clicksintegerrequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/onsite" \
  -H "Authorization: Bearer rb_live_xxxxx"
post/onsite

Create an on-site message

Scope: settings:writeoperationId: createOnsiteMessage

A banner or a slide-in on your own pages, separate from push.

Request body · application/json

  • titlestring

    Up to 120 characters. Required on create (400 title_required); on PUT, left out keeps the old one.

  • bodystring

    Up to 300 characters.

  • ctaTextstring

    Button text, up to 40 characters.

  • ctaUrlstring

    Button link, up to 500 characters.

  • stylestring

    Default "slidein".

    One of: slidein, banner. Default "slidein".

  • activeboolean

    Default true.

Responses

  • 201Created.
    Response fields
  • 400Bad request

    invalid_jsontitle_required

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    payload_too_large

Example

cURL
curl -X POST https://relaybell.com/api/v1/onsite \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "title": "Free shipping this week",
    "body": "On every order over $50.",
    "ctaText": "Shop now",
    "ctaUrl": "https://shop.example",
    "style": "banner"
  }'
put/onsite/{id}

Replace an on-site message

Scope: settings:writeoperationId: updateOnsiteMessage

Replaces every field (a field left out is cleared, active goes back to true unless false) — except title, which is kept when left out.

Path parameters

  • idstring (uuid)required

    The message's id. One that isn't this website's — or isn't a UUID — answers 404 not_found.

Request body · application/json

The same fields as Create an on-site message.

Responses

  • 200The message now.
    Response fields
  • 400Bad request

    invalid_json

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

  • 413Too large

    payload_too_large

Example

cURL
curl -X PUT https://relaybell.com/api/v1/onsite/ID \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "title": "Free shipping this week",
    "style": "banner",
    "active": false
  }'
delete/onsite/{id}

Delete an on-site message

Scope: settings:writeoperationId: deleteOnsiteMessage

An unknown id answers { ok: true }.

Path parameters

  • idstring (uuid)required

    The message id (not a UUID: 404 not_found).

Responses

  • 200Done.
    Response fields
    • oktruerequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 404Not found

    not_found

Example

cURL
curl -X DELETE https://relaybell.com/api/v1/onsite/ID \
  -H "Authorization: Bearer rb_live_xxxxx"

Stats and reports

Totals, daily numbers, growth and list health.

get/stats

Get totals and recent sends

Any API keyoperationId: getStats

Totals for the website, plus its 20 most recent sends (newest first). subscribers counts browsers (one person on two devices counts twice).

Responses

  • 200The stats.
    Response fields
    • subscribersintegerrequired
    • subscriberDevicesmap of integerrequired

      By device: desktop, android, ios, mobile, other.

    • totalsobjectrequired
    • totals.sentintegerrequired
    • totals.clicksintegerrequired
    • totals.ctrnumberrequired
    • totals.conversionsintegerrequired
    • totals.revenuenumberrequired
    • notificationsarray of objectrequired
    • notifications[].idstring (uuid)required
    • notifications[].titlestringrequired
    • notifications[].bodystring or null
    • notifications[].urlstring or null
    • notifications[].statusstringrequired

      draft, scheduled, queued, held_for_review, sending, sent, failed (reason in error) or canceled.

      One of: draft, scheduled, queued, held_for_review, sending, sent, failed, canceled.

    • notifications[].recipientsinteger
    • notifications[].sentinteger
    • notifications[].failedinteger
    • notifications[].prunedinteger
    • notifications[].receivedinteger

      Pushes the browser confirmed it showed.

    • notifications[].clicksinteger
    • notifications[].conversionsinteger
    • notifications[].revenuenumber
    • notifications[].devicesmap of integer
    • notifications[].abobject or null
    • notifications[].abStatsobject or null

      { a: { delivered, clicks }, b: { delivered, clicks } } for an A/B send.

    • notifications[].errorstring or null
    • notifications[].scheduled_atstring (date-time) or null
    • notifications[].created_atstring (date-time)required
    • promptAbobject or nullrequired

      Opt-in prompt A/B results, or null.

    • emailVerifiedboolean or nullrequired
    • sendingBlockedbooleanrequired

      Sends are refused until the email is verified.

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

curl "https://relaybell.com/api/v1/stats" \
  -H "Authorization: Bearer rb_live_xxxxx"
get/stats/timeseries

Get daily numbers

Any API keyoperationId: getTimeseries

One point per day (days without sends are zeros), the totals, and the same totals for the period of the same length just before. Each notification counts on the day it went out. Without from and to it covers the last 30 days; at most 366 days.

Query parameters

  • fromstring (date)

    First day (YYYY-MM-DD).

  • tostring (date)

    Last day (YYYY-MM-DD, included; default today).

  • tzstring

    IANA time zone (default: the website's, else UTC).

Responses

  • 200The series.
    Response fields
    • fromstring (date)required
    • tostring (date)required
    • tzstringrequired
    • interval"day"required
    • pointsarray of objectrequired
    • points[].datestring (date)
    • points[].sentinteger
    • points[].deliveredinteger
    • points[].clickedinteger
    • points[].conversionsinteger
    • points[].revenuenumber
    • totalsobjectrequired
    • totals.sentinteger
    • totals.deliveredinteger
    • totals.clickedinteger
    • totals.ctrnumber
    • totals.conversionsinteger
    • totals.revenuenumber
    • previousobjectrequired
    • previous.fromstring
    • previous.tostring
    • previous.sentinteger
    • previous.deliveredinteger
    • previous.clickedinteger
    • previous.ctrnumber
    • previous.conversionsinteger
    • previous.revenuenumber
    • currencystring or nullrequired
  • 400Bad request

    invalid_rangeinvalid_tz

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/stats/timeseries" \
  -H "Authorization: Bearer rb_live_xxxxx"
get/stats/growth

Get subscriber growth

Any API keyoperationId: getGrowth

New and lost subscribers per day (days with neither are left out), oldest first.

Query parameters

  • daysinteger

    1–365 (default 30).

    Default 30.

Responses

  • 200The growth.
    Response fields
    • daysintegerrequired
    • seriesarray of objectrequired
    • series[].datestring (date)
    • series[].subscribedinteger
    • series[].churnedinteger
    • totalsobjectrequired
    • totals.subscribedinteger
    • totals.churnedinteger
    • totals.netinteger
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/stats/growth" \
  -H "Authorization: Bearer rb_live_xxxxx"
get/stats/health

Get list health

Any API keyoperationId: getListHealth

active (not unsubscribed), reachable (devices of active subscribers), unsubscribed, new30 / lost30 (last 30 days), dormant (no delivery in 60 days, older than a week) and churnRate (%).

Responses

  • 200The numbers.
    Response fields
    • activeintegerrequired
    • reachableintegerrequired
    • unsubscribedintegerrequired
    • new30integerrequired
    • lost30integerrequired
    • dormantintegerrequired
    • churnRatenumberrequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/stats/health" \
  -H "Authorization: Bearer rb_live_xxxxx"
post/reports/test

Email a test report now

Scope: settings:writeoperationId: sendTestReport

Sends the performance report (last 7 days, or 30 with frequency: "monthly") to the account's email now.

Request body · application/json · optional

  • frequencystring

    One of: weekly, monthly.

Responses

  • 200Sent.
    Response fields
    • oktruerequired
    • sentTostringrequired
    • reportobjectrequired
    • report.sentinteger
    • report.clicksinteger
    • report.ctrnumber
    • report.conversionsinteger
    • report.revenuenumber
    • report.newSubsinteger
    • report.topobject or null

      { title, clicks } of the best send, or null.

  • 400Bad request

    invalid_jsonno_account_email

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    payload_too_large

Example

cURL
curl -X POST https://relaybell.com/api/v1/reports/test \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "frequency": "weekly"
  }'

Website settings

Sending rules, the opt-in prompt, the welcome push and the default logo.

get/me

Get the website

Any API keyoperationId: getProject

The website the key belongs to: its id, name, VAPID public key, logo and number of stored push subscriptions.

Responses

  • 200The website.
    Response fields
    • projectIdstring (uuid)required
    • namestring
    • vapidPublicKeystring
    • logostring or null
    • subscribersinteger

      Stored push subscriptions.

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/me" \
  -H "Authorization: Bearer rb_live_xxxxx"
get/setup-status

Get setup progress

Any API keyoperationId: getSetupStatus

Whether the SDK has been seen on the live site, and whether there are subscribers, sends, a key and an automation.

Responses

  • 200The status.
    Response fields
    • installedbooleanrequired
    • sdkLastSeenAtstring (date-time) or null
    • subscribersintegerrequired
    • sentintegerrequired

      Notifications made (drafts not counted).

    • hasApiKeybooleanrequired
    • hasAutomationbooleanrequired

      Any automation, journey, schedule or feed.

    • emailVerifiedboolean or null
    • sendingBlockedbooleanrequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/setup-status" \
  -H "Authorization: Bearer rb_live_xxxxx"
get/settings

Get website settings

Any API keyoperationId: getSettings

A key without full or settings:write doesn't get shopifySecret back.

Responses

  • 200The settings.
    Response fields
    • settingsSettingsrequired

      Website settings. Other keys are stored as they are.

    • settings.frequencyCapinteger

      Most pushes one browser gets in 24 hours; 0 or unset = no cap.

    • settings.quietHoursobject
    • settings.defaultUrlstring

      The link used when a send has no url ("" or null removes it).

    • settings.utmobject
    • settings.timezonestring

      The website's time zone for reports (IANA; 400 invalid_timezone; "" or null removes it).

    • settings.retentionDaysinteger

      Delete activity history older than this; 0 = keep forever (400 invalid_retention_days).

      Range: 0–3650.

    • settings.privacyUrlstring

      Linked from the opt-in prompt.

    • settings.conversionEventsarray of string

      Which events count as conversions.

    • settings.attributionWindowDaysinteger

      Days after a delivery a conversion is credited to it (default 7).

    • settings.currencystring

      Revenue currency.

    • settings.reportobject

      The emailed performance report.

    • settings.shopifySecretstring

      Your Shopify webhook signing secret. Not returned to a key without full or settings:write.

    • settings.identityVerificationboolean

      Read-only here; change it with PUT /identity.

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/settings" \
  -H "Authorization: Bearer rb_live_xxxxx"
put/settings

Change website settings

Scope: settings:writeoperationId: updateSettings

Merges the keys you send into the stored ones ({ "settings": { … } } or the keys at the top level). identityVerification is ignored here — use PUT /identity.

Request body · application/json

  • settingsSettingsrequired

    Website settings. Other keys are stored as they are.

    Its fields: see Get website settings.

Responses

  • 200The settings now.
    Response fields
    • oktruerequired
    • settingsSettingsrequired

      Website settings. Other keys are stored as they are.

      Its fields: see Get website settings.

  • 400Bad request

    invalid_jsoninvalid_settingsinvalid_timezoneinvalid_retention_days

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    payload_too_large

Example

cURL
curl -X PUT https://relaybell.com/api/v1/settings \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "settings": {
      "frequencyCap": 3,
      "quietHours": {
        "enabled": true,
        "start": "22:00",
        "end": "08:00",
        "timezone": "Europe/London"
      }
    }
  }'
get/prompt

Get the opt-in prompt

Any API keyoperationId: getPrompt

The opt-in prompt the SDK shows.

Responses

  • 200The prompt.
    Response fields
    • promptPromptConfig or any or nullrequired
    • prompt.enabledboolean
    • prompt.typestring

      One of: slidein, card, bar, modal, native.

    • prompt.triggerstring

      One of: delay, load, click, scroll, exit.

    • prompt.delaySecondsinteger

      Range: 0–600.

    • prompt.scrollPercentinteger

      Range: 1–100.

    • prompt.clickSelectorstring or null
    • prompt.titlestring

      Range: at most 120.

    • prompt.messagestring

      Range: at most 300.

    • prompt.allowTextstring

      Range: at most 40.

    • prompt.denyTextstring

      Range: at most 40.

    • prompt.positionstring

      One of: bottom-right, bottom-left, top-right, top-left, top, bottom.

    • prompt.accentColorstring

      A color code like #0B7C93 (400 invalid_color).

    • prompt.iosInstallHintboolean
    • prompt.bobject

      Prompt A/B test: version B.

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/prompt" \
  -H "Authorization: Bearer rb_live_xxxxx"
put/prompt

Change the opt-in prompt

Scope: settings:writeoperationId: updatePrompt

Replaces the prompt ({ "prompt": { … } } or the fields at the top level). Only known fields are stored, with safe values.

Request body · application/json

  • promptPromptConfigrequired

    The opt-in prompt. Only these fields are stored; unknown values fall back to the defaults.

    Its fields: see Get the opt-in prompt.

Responses

  • 200The prompt now.
    Response fields
    • oktruerequired
    • promptPromptConfigrequired

      The opt-in prompt. Only these fields are stored; unknown values fall back to the defaults.

      Its fields: see Get the opt-in prompt.

  • 400Bad request

    invalid_jsoninvalid_promptinvalid_color

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    payload_too_large

Example

cURL
curl -X PUT https://relaybell.com/api/v1/prompt \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "prompt": {
      "type": "slidein",
      "trigger": "delay",
      "delaySeconds": 5,
      "title": "Get deals first",
      "allowText": "Allow",
      "accentColor": "#0B7C93"
    }
  }'
get/welcome

Get the welcome push

Any API keyoperationId: getWelcome

The push sent straight away to each new browser.

Responses

  • 200The welcome config.
    Response fields
    • welcomeWelcomeConfig or any or nullrequired
    • welcome.enabledboolean
    • welcome.titlestring
    • welcome.bodystring
    • welcome.urlstring
    • welcome.iconstring
    • welcome.imagestring
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/welcome" \
  -H "Authorization: Bearer rb_live_xxxxx"
put/welcome

Change the welcome push

Scope: settings:writeoperationId: updateWelcome

{ "welcome": { enabled, title, body, url, icon, image } } (or the fields at the top level). It replaces the whole config.

Request body · application/json

  • welcomeWelcomeConfigrequired

    The welcome push sent straight away to each new browser. Replaces the whole config.

    Its fields: see Get the welcome push.

Responses

  • 200The welcome config now.
    Response fields
    • oktruerequired
    • welcomeWelcomeConfigrequired

      The welcome push sent straight away to each new browser. Replaces the whole config.

      Its fields: see Get the welcome push.

  • 400Bad request

    invalid_jsoninvalid_welcome

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    payload_too_large

Example

cURL
curl -X PUT https://relaybell.com/api/v1/welcome \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "welcome": {
      "enabled": true,
      "title": "Thanks for subscribing!",
      "body": "We'll only send what matters.",
      "url": "https://shop.example"
    }
  }'

API keys

List and revoke keys (creating one needs the dashboard).

get/keys

List API keys

Scope: fulloperationId: listApiKeys

The website's keys (never the keys themselves) and the scopes a key can have. Needs full — the only scope that can list keys.

Responses

  • 200The keys.
    Response fields
    • keysarray of ApiKeyrequired
    • keys[].idstring (uuid)required
    • keys[].prefixstringrequired

      The visible start of the key.

    • keys[].labelstring or null
    • keys[].namestring or null
    • keys[].scopesarray of stringrequired
    • keys[].expires_atstring (date-time) or null
    • keys[].created_atstring (date-time)required
    • keys[].last_used_atstring (date-time) or null
    • keys[].expiredbooleanrequired
    • keys[].currentbooleanrequired

      Whether this is the key making the request.

    • scopesarray of stringrequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

Example

cURL
curl "https://relaybell.com/api/v1/keys" \
  -H "Authorization: Bearer rb_live_xxxxx"
post/keys

Create an API key

Dashboard session onlyoperationId: createApiKey

Dashboard session only — a key can't make other keys (403 session_required). The answer's apiKey is the only time you see the key: relaybell stores only a hash. The owner gets an email about every new key.

Headers

  • X-Project-Idstring (uuid)required

    The website the dashboard session acts on (or ?projectId= / projectId in the body). Missing: 400 project_id_required; not one of yours: 404 project_not_found.

Request body · application/json · optional

  • namestring

    Up to 60 characters (default "API key").

  • scopesarray of string

    Default ["full"]; a list with full becomes just ["full"] (400 invalid_scopes).

  • expiresAtstring (date-time) or null

    In the future, within 3 years, or null for none (400 invalid_expiry).

Responses

  • 201Created.
    Response fields
    • apiKeystringrequired

      The key — shown once.

    • keyApiKeyrequired

      Its fields: see List API keys.

  • 400Bad request

    invalid_jsoninvalid_scopesinvalid_expiryproject_id_required

  • 401No valid credentials

    not_authenticated

  • 403Not allowed

    account_suspendedsession_required

  • 404Not found

    project_not_found

  • 413Too large

    payload_too_large

Only the signed-in dashboard calls this route: do it under Settings → Developers.

delete/keys/{id}

Revoke an API key

Any API keyoperationId: revokeApiKey

The signed-in owner can revoke any key. An API key can revoke only itself (:id = its own id, or current); anything else answers 403 session_required.

Path parameters

  • idstringrequired

    The key id, or current for the key making the request.

Responses

  • 200Done.
    Response fields
    • oktruerequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    session_requiredaccount_suspended

  • 404Not found

    not_found

Example

cURL
curl -X DELETE https://relaybell.com/api/v1/keys/ID \
  -H "Authorization: Bearer rb_live_xxxxx"

Identity verification

The secret your server signs user ids with, so nobody can claim another person's notifications.

get/identity

Get the identity secret

Any API keyoperationId: getIdentity

The secret key your server signs external ids with (userHash = lowercase hex HMAC-SHA256 of the external id), and whether verified ids are required. Creates the secret on the first call. Never cached.

Responses

  • 200The secret.
    Response fields
    • secretstringrequired

      64 hex characters, used as text.

    • requiredbooleanrequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/identity" \
  -H "Authorization: Bearer rb_live_xxxxx"
put/identity

Require verified user ids

Any API keyoperationId: setIdentityRequired

true turns the requirement on (and creates the secret if needed). false turns it off — from the dashboard only: with an API key it answers 403 identity_off_requires_dashboard, so a leaked key can't switch the protection off.

Request body · application/json

  • requiredbooleanrequired

Responses

  • 200The setting now.
    Response fields
    • requiredbooleanrequired
  • 400Bad request

    invalid_jsoninvalid_required

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    identity_off_requires_dashboardaccount_suspended

  • 413Too large

    payload_too_large

Example

cURL
curl -X PUT https://relaybell.com/api/v1/identity \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "required": true
  }'
post/identity/rotate

Make a new identity secret

Dashboard session onlyoperationId: rotateIdentitySecret

Dashboard session only (an API key gets 403 session_required). The old secret stops working at once, so update your site straight away.

Headers

  • X-Project-Idstring (uuid)required

    The website the dashboard session acts on (or ?projectId= / projectId in the body). Missing: 400 project_id_required; not one of yours: 404 project_not_found.

Responses

  • 200The new secret.
    Response fields
    • secretstringrequired
  • 400Bad request

    project_id_required

  • 401No valid credentials

    not_authenticated

  • 403Not allowed

    account_suspendedsession_required

  • 404Not found

    project_not_found

Only the signed-in dashboard calls this route: do it under Settings → Developers.

Uploads and AI

Image uploads and copy suggestions.

post/upload

Upload an image

Scope: sendoperationId: uploadImage

An image for a notification: PNG, JPEG, GIF or WebP up to 2 MB, as the multipart form field file. 60 uploads an hour.

Request body · multipart/form-data

  • filestringrequired

    The image.

Responses

  • 201Uploaded.
    Response fields
    • urlstringrequired

      The public URL of the image.

  • 400Bad request

    invalid_file

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspendedinsufficient_scope

  • 413Too large

    file_too_large

  • 429Too many requests

    rate_limited

Example

cURL
curl -X POST https://relaybell.com/api/v1/upload \
  -H "Authorization: Bearer rb_live_xxxxx" \
  -F "file=@notification-image.png"
post/ai/suggest

Suggest notification copy

Any API keyoperationId: suggestCopy

Three rewordings of your draft for you to edit — written by an AI model when ai is true, made from simple templates when it is false. 30 requests an hour.

Request body · application/json

  • titlestring
  • bodystring
  • goalstring

    e.g. "more clicks".

Responses

  • 200Suggestions.
    Response fields
    • aibooleanrequired
    • suggestionsarray of objectrequired
    • suggestions[].titlestring
    • suggestions[].bodystring
  • 400Bad request

    invalid_json

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

  • 413Too large

    payload_too_large

  • 429Too many requests

    rate_limited

Example

cURL
curl -X POST https://relaybell.com/api/v1/ai/suggest \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "title": "New arrivals",
    "body": "Fresh styles just landed",
    "goal": "more clicks"
  }'

Test devices

Your own browsers, for test sends that never reach subscribers.

get/test-devices

List test devices

Any API keyoperationId: listTestDevices

Your own browsers marked as test devices, so "send test to me" never reaches real subscribers.

Responses

  • 200The devices.
    Response fields
    • devicesarray of objectrequired
    • devices[].idstring (uuid)

      The subscriber id.

    • devices[].devicestring

      e.g. "Chrome on Windows".

    • devices[].lastSeenAtstring (date-time) or null
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

Example

cURL
curl "https://relaybell.com/api/v1/test-devices" \
  -H "Authorization: Bearer rb_live_xxxxx"
post/test-devices

Mark a subscribed browser as a test device

Any API keyoperationId: addTestDevice

Marks the browser with this push endpoint (it must already be subscribed to this website: 404 not_subscribed). Up to 20 test devices (409 too_many_test_devices).

Request body · application/json

  • endpointstringrequired

    The browser's push endpoint URL.

Responses

  • 200Done.
    Response fields
    • oktruerequired
  • 400Bad request

    invalid_json

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

  • 404Not found

    not_subscribed

  • 409Conflict

    too_many_test_devices

  • 413Too large

    payload_too_large

Example

cURL
curl -X POST https://relaybell.com/api/v1/test-devices \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "endpoint": "https://fcm.googleapis.com/fcm/send/…"
  }'
delete/test-devices/{subscriberId}

Remove a test device

Any API keyoperationId: removeTestDevice

Takes the test mark off (the browser stays subscribed).

Path parameters

  • subscriberIdstring (uuid)required

    The id from GET /test-devices (not a UUID: 404 not_found).

Responses

  • 200Done.
    Response fields
    • oktruerequired
  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

  • 404Not found

    not_found

Example

cURL
curl -X DELETE https://relaybell.com/api/v1/test-devices/SUBSCRIBER_ID \
  -H "Authorization: Bearer rb_live_xxxxx"
post/test-devices/send

Send a test to your test devices

Any API keyoperationId: sendTestToDevices

Sends right now to your test devices only (it never shows up in reports). One result per push endpoint: sent, expired (removed), key_mismatch (the browser must subscribe again) or failed (with reason server_auth or timeout). Test sends work before the email is verified (warning: "email_not_verified") and have their own budget of 120 per clock hour.

Request body · application/json

  • titlestringrequired

    Required (400 title_required).

  • bodystring
  • urlstring
  • iconstring
  • imagestring
  • actionsarray of Action

    Its fields: see Send a notification.

  • requireInteractionboolean

Responses

  • 200How it went.
    Response fields
    • devicesintegerrequired
    • sentintegerrequired
    • failedintegerrequired
    • resultsarray of objectrequired
    • results[].idstring (uuid)
    • results[].devicestring
    • results[].statusstring

      One of: sent, expired, key_mismatch, failed.

    • results[].codeinteger or null

      The push service's HTTP status.

    • results[].reasonstring

      One of: server_auth, timeout.

    • warning"email_not_verified"
  • 400Bad request

    invalid_jsontitle_required

  • 401No valid credentials

    not_authenticatedinvalid_api_keyapi_key_expired

  • 403Not allowed

    account_suspended

  • 409Conflict

    no_test_devices

  • 413Too large

    payload_too_large

  • 429Too many requests

    send_limit_reached

Example

cURL
curl -X POST https://relaybell.com/api/v1/test-devices/send \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{
    "title": "Test from relaybell",
    "body": "It works!",
    "url": "https://shop.example"
  }'

Browser SDK

Public routes the browser SDK and service worker call with your Project ID — no API key. 120 requests a minute per visitor IP.

get/projects/{projectId}/config

Get the SDK config

Public · no API keyoperationId: getProjectConfig

What the browser SDK needs to subscribe a visitor: the VAPID public key, the opt-in prompt (with your privacy-policy link), topics and the active on-site messages. 120 requests a minute per visitor IP.

Path parameters

  • projectIdstring (uuid)required

    Your Project ID (public: it is in every page that loads the SDK). One that is not a UUID or not a website answers 404 project_not_found.

Query parameters

  • fromstring

    "wp-admin" from the WordPress plugin's settings page (then it doesn't count as the SDK being seen on the live site).

Responses

  • 200The config.
    Response fields
    • vapidPublicKeystringrequired
    • promptobjectrequired

      The prompt config, plus privacyUrl; enabled is false for a suspended account.

    • topicsarray of objectrequired
    • topics[].namestring
    • topics[].slugstring
    • onsiteMessagesarray of objectrequired
    • onsiteMessages[].idstring (uuid)
    • onsiteMessages[].titlestring
    • onsiteMessages[].bodystring or null
    • onsiteMessages[].ctaTextstring or null
    • onsiteMessages[].ctaUrlstring or null
    • onsiteMessages[].stylestring
  • 404Not found

    project_not_found

  • 429Too many requests

    rate_limited

Example

cURL
curl "https://relaybell.com/api/v1/projects/PROJECT_ID/config"
post/projects/{projectId}/subscriptions

Save a push subscription

Public · no API keyoperationId: subscribe

Called by the SDK when a visitor allows notifications (201 for a new browser, 200 when it was already known). externalId links the browser to your user; with identity verification it needs a valid userHash, else it is ignored for this request and identity says unverified. oldEndpoint (from the service worker) moves a replaced subscription onto the new endpoint under the same subscriber. A new browser gets your welcome push and fires subscriber.created.

Path parameters

  • projectIdstring (uuid)required

    Your Project ID (public: it is in every page that loads the SDK). One that is not a UUID or not a website answers 404 project_not_found.

Request body · application/json

  • subscriptionobjectrequired
  • subscription.endpointstringrequired

    An https push-service URL (400 invalid_subscription otherwise).

  • subscription.keysobjectrequired
  • keys.p256dhstring
  • keys.authstring
  • externalIdstring

    Your user id.

  • userHashstring

    HMAC-SHA256 of externalId with your identity secret, lowercase hex.

  • urlstring

    The page the visitor is on.

  • languagestring
  • timezonestring
  • tagsmap of string or number or boolean or null

    rb_ keys are ignored.

  • policyVersionstring
  • promptVariantstring

    One of: a, b.

  • oldEndpointstring

Responses

  • 200Already known: updated.
    Response fields
    • oktruerequired
    • endpointIdstring (uuid)required
    • identitystring

      Only when an externalId was sent: verified, unverified (ignored for this request) or unchecked (no hash, verification not required).

      One of: verified, unverified, unchecked.

  • 201Subscribed.
    Response fields
    • oktruerequired
    • endpointIdstring (uuid)required
    • identitystring

      Only when an externalId was sent: verified, unverified (ignored for this request) or unchecked (no hash, verification not required).

      One of: verified, unverified, unchecked.

  • 400Bad request

    invalid_jsoninvalid_subscription

  • 404Not found

    project_not_found

  • 413Too large

    payload_too_large

  • 429Too many requests

    rate_limited

Example

cURL
curl -X POST https://relaybell.com/api/v1/projects/PROJECT_ID/subscriptions \
  -H "Content-Type: application/json" \
  -d '{
    "subscription": {
      "endpoint": "https://fcm.googleapis.com/fcm/send/…",
      "keys": {
        "p256dh": "BNcRdreALRFX…",
        "auth": "tBHItJI5svbp…"
      }
    },
    "externalId": "jane@example.com",
    "userHash": "<signature made on your server>",
    "url": "https://shop.example/",
    "language": "en-GB"
  }'
delete/projects/{projectId}/subscriptions

Unsubscribe a browser

Public · no API keyoperationId: unsubscribe

Called by RelayBell.unsubscribe(): removes the endpoint, marks the subscriber unsubscribed and fires subscriber.unsubscribed. An unknown endpoint answers { ok: true, alreadyGone: true }.

Path parameters

  • projectIdstring (uuid)required

    Your Project ID (public: it is in every page that loads the SDK). One that is not a UUID or not a website answers 404 project_not_found.

Query parameters

  • endpointstring

    The push endpoint (or in the body).

Request body · application/json · optional

  • endpointstring

Responses

  • 200Done.
    Response fields
    • oktruerequired
    • unsubscribedboolean
    • alreadyGoneboolean
  • 400Bad request

    invalid_jsonendpoint_required

  • 404Not found

    project_not_found

  • 413Too large

    payload_too_large

  • 429Too many requests

    rate_limited

Example

cURL
curl -X DELETE https://relaybell.com/api/v1/projects/PROJECT_ID/subscriptions
post/projects/{projectId}/events

Track an event from the browser

Public · no API keyoperationId: trackBrowserEvent

What RelayBell.track() calls. Only the browser's own push endpoint identifies the person here (a bare externalId is stored only when verified, or when no hash is sent and verification isn't required), and it never starts a product-alert broadcast.

Path parameters

  • projectIdstring (uuid)required

    Your Project ID (public: it is in every page that loads the SDK). One that is not a UUID or not a website answers 404 project_not_found.

Request body · application/json

  • eventstring

    Or name. Missing: 400 event_required.

  • namestring
  • propertiesobject
  • endpointstring
  • externalIdstring
  • userHashstring

Responses

  • 202Stored.
    Response fields
    • oktruerequired
    • eventIdstringrequired
  • 400Bad request

    invalid_jsonevent_required

  • 404Not found

    project_not_found

  • 413Too large

    payload_too_large

  • 429Too many requests

    rate_limited

Example

cURL
curl -X POST https://relaybell.com/api/v1/projects/PROJECT_ID/events \
  -H "Content-Type: application/json" \
  -d '{
    "event": "product.viewed",
    "properties": {
      "productId": "1234"
    },
    "endpoint": "https://fcm.googleapis.com/fcm/send/…"
  }'
post/projects/{projectId}/prompt-event

Record that the prompt was shown

Public · no API keyoperationId: recordPromptShown

For the opt-in prompt A/B results.

Path parameters

  • projectIdstring (uuid)required

    Your Project ID (public: it is in every page that loads the SDK). One that is not a UUID or not a website answers 404 project_not_found.

Request body · application/json · optional

  • variantstring

    "b", else counted as "a".

    One of: a, b.

Responses

  • 202Recorded.
    Response fields
    • oktruerequired
  • 400Bad request

    invalid_json

  • 404Not found

    project_not_found

  • 413Too large

    payload_too_large

  • 429Too many requests

    rate_limited

Example

cURL
curl -X POST https://relaybell.com/api/v1/projects/PROJECT_ID/prompt-event
post/projects/{projectId}/onsite-event

Record an on-site message view or click

Public · no API keyoperationId: recordOnsiteEvent

An impression (default) or a click on one of the website's on-site messages; unknown messages are ignored.

Path parameters

  • projectIdstring (uuid)required

    Your Project ID (public: it is in every page that loads the SDK). One that is not a UUID or not a website answers 404 project_not_found.

Request body · application/json

  • messageIdstring (uuid)required

    400 invalid_message when missing or not a UUID.

  • typestring

    One of: impression, click. Default "impression".

Responses

  • 202Recorded.
    Response fields
    • oktruerequired
  • 400Bad request

    invalid_jsoninvalid_message

  • 404Not found

    project_not_found

  • 413Too large

    payload_too_large

  • 429Too many requests

    rate_limited

Example

cURL
curl -X POST https://relaybell.com/api/v1/projects/PROJECT_ID/onsite-event \
  -H "Content-Type: application/json" \
  -d '{
    "messageId": "0a2c4e6f-8b0d-4c2e-a4f6-8b0d2e4f6a8c",
    "type": "click"
  }'
get/projects/{projectId}/inbox

Get the public inbox

Public · no API keyoperationId: getInbox

The 15 latest broadcasts for showInbox(). Transactional, automation, journey and per-person sends are never listed.

Path parameters

  • projectIdstring (uuid)required

    Your Project ID (public: it is in every page that loads the SDK). One that is not a UUID or not a website answers 404 project_not_found.

Responses

  • 200The inbox.
    Response fields
    • notificationsarray of objectrequired
    • notifications[].idstring (uuid)
    • notifications[].titlestring
    • notifications[].bodystring or null
    • notifications[].urlstring or null
    • notifications[].iconstring or null
    • notifications[].imagestring or null
    • notifications[].created_atstring (date-time)
  • 404Not found

    project_not_found

  • 429Too many requests

    rate_limited

Example

cURL
curl "https://relaybell.com/api/v1/projects/PROJECT_ID/inbox"
get/projects/{projectId}/preferences

Get a browser's topics

Public · no API keyoperationId: getPreferences

The topic slugs the browser with this endpoint opted in to (empty for an unknown browser).

Path parameters

  • projectIdstring (uuid)required

    Your Project ID (public: it is in every page that loads the SDK). One that is not a UUID or not a website answers 404 project_not_found.

Query parameters

  • endpointstring

    The browser's push endpoint.

Responses

  • 200The topics.
    Response fields
    • topicsarray of stringrequired
  • 404Not found

    project_not_found

  • 429Too many requests

    rate_limited

Example

cURL
curl "https://relaybell.com/api/v1/projects/PROJECT_ID/preferences"
put/projects/{projectId}/preferences

Set a browser's topics

Public · no API keyoperationId: setPreferences

Replaces the browser's topics with these slugs (unknown slugs are skipped). A browser that isn't subscribed: 404 subscriber_not_found.

Path parameters

  • projectIdstring (uuid)required

    Your Project ID (public: it is in every page that loads the SDK). One that is not a UUID or not a website answers 404 project_not_found.

Request body · application/json

  • endpointstringrequired
  • topicsarray of string

Responses

  • 200Applied.
    Response fields
    • oktruerequired
    • appliedintegerrequired

      Topics set.

  • 400Bad request

    invalid_json

  • 404Not found

    project_not_foundsubscriber_not_found

  • 413Too large

    payload_too_large

  • 429Too many requests

    rate_limited

Example

cURL
curl -X PUT https://relaybell.com/api/v1/projects/PROJECT_ID/preferences \
  -H "Content-Type: application/json" \
  -d '{
    "endpoint": "https://fcm.googleapis.com/fcm/send/…",
    "topics": [
      "deals",
      "news"
    ]
  }'
post/notifications/{id}/click

Record a click

Public · no API keyoperationId: recordClick

Called by relaybell's service worker when a notification is clicked; you don't call it yourself. It needs the signed token from the push payload: a missing or forged one, or an unknown notification, answers 204 and records nothing. Replays are ignored. Fires notification.clicked.

Path parameters

  • idstring (uuid)required

    The notification id.

Query parameters

  • tokenstring

    The signed token (or in the body).

Request body · application/json · optional

  • tokenstring
  • epstring

    The per-recipient endpoint id from the payload.

Responses

  • 200Recorded.
    Response fields
    • oktruerequired
  • 204Ignored (no valid token, or no such notification).
  • 400Bad request

    invalid_json

  • 413Too large

    payload_too_large

  • 429Too many requests

    rate_limited

Example

cURL
curl -X POST https://relaybell.com/api/v1/notifications/ID/click
post/notifications/{id}/receipt

Record a delivery receipt

Public · no API keyoperationId: recordReceipt

Called by relaybell's service worker when a push reaches the device (the delivered count in reports); you don't call it yourself. Same signed token and replay protection as clicks.

Path parameters

  • idstring (uuid)required

    The notification id.

Query parameters

  • tokenstring

    The signed token (or in the body).

Request body · application/json · optional

  • tokenstring
  • epstring

Responses

  • 200Recorded.
    Response fields
    • oktruerequired
  • 204Ignored (no valid token).
  • 400Bad request

    invalid_json

  • 413Too large

    payload_too_large

  • 429Too many requests

    rate_limited

Example

cURL
curl -X POST https://relaybell.com/api/v1/notifications/ID/receipt
post/test-devices/claim

Mark this browser as a test device

Public · no API keyoperationId: claimTestDevice

Called by the SDK on a page opened with ?rb_test=CODE (from POST /test-devices/link); the code is the credential.

Request body · application/json

  • projectIdstring (uuid)required
  • codestringrequired
  • endpointstringrequired

Responses

  • 200Done.
    Response fields
    • oktruerequired
  • 400Bad request

    invalid_jsoninvalid_requestinvalid_code

  • 404Not found

    not_subscribed

  • 409Conflict

    too_many_test_devices

  • 413Too large

    payload_too_large

  • 429Too many requests

    rate_limited

Example

cURL
curl -X POST https://relaybell.com/api/v1/test-devices/claim \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "8c6f1e2a-4b7d-4f3a-9e21-5d0c7b3a1f64",
    "code": "q3Vx9TzA1bCd",
    "endpoint": "https://fcm.googleapis.com/fcm/send/…"
  }'

Integrations

Inbound webhooks from other platforms.

post/integrations/shopify/{projectId}

Receive a Shopify webhook

Public · no API keyoperationId: shopifyWebhook

Point your Shopify webhooks here. checkouts/create, checkouts/update and carts/update become cart.updated; orders/paid or orders/create become purchase (subscribe to only one); orders/fulfilled becomes order.fulfilled. The person is found by the customer's email (their externalId). Other topics are acknowledged and ignored.

Requests must be signed with your Shopify signing secret (Settings → Privacy): a wrong X-Shopify-Hmac-Sha256 answers 401 invalid_hmac; without a saved secret every request is acknowledged (200) and ignored. Errors on relaybell's side also answer 200 so Shopify doesn't retry.

Path parameters

  • projectIdstring (uuid)required

    Your Project ID. Unknown: 404 project_not_found.

Headers

  • X-Shopify-Hmac-Sha256stringrequired

    Shopify's base64 HMAC-SHA256 of the raw body.

  • X-Shopify-Topicstringrequired

    e.g. orders/paid.

  • X-Shopify-Shop-Domainstring

    Used for a cart link when your website address isn't set.

Request body · application/json

The Shopify order, checkout or cart.

Responses

  • 200Acknowledged.
    Response fields
    • okbooleanrequired
    • eventstring

      The relaybell event it became.

    • ignoredstring

      A topic that isn't mapped.

    • error"shopify_secret_required"
  • 400Bad request

    invalid_json

  • 401No valid credentials

    invalid_hmac

  • 404Not found

    project_not_found

  • 413Too large

    payload_too_large

Example

cURL
curl -X POST https://relaybell.com/api/v1/integrations/shopify/PROJECT_ID