ChangelogOpen dashboard

Notifications

5 endpoints

A campaign's notifications — nested under it, since a notification has no meaning until a campaign decides when it fires. Creating one here writes its content (headline, body, media, CTA, survey questions) and its trigger together; there's no separate endpoint for notification content alone.

List a campaign's notifications#

GET/campaigns/{campaign}/notifications
Needs notifications:readNo token needed

Path parameters

campaign
string (uuid)
required

The campaign ID

Responses

200
200 response
{
  "data": [
    {
      "id": "0192f0a4-6b1e-7c3a-9d2f-5e8b1a4d5001",
      "campaign_id": "0192f0a4-6b1e-7c3a-9d2f-5e8b1a4d4001",
      "headline": "Coffee's on us ☕",
      "body": "Show this at the counter for a free flat white before 11am.",
      "media_asset_id": null,
      "cta_label": "Get the voucher",
      "cta_url": "https://harbour.coffee/free",
      "trigger_type": "on_enter",
      "created_at": "2026-09-20T09:12:00Z"
    }
  ],
  "meta": {
    "next_cursor": null
  }
}
Response fields
data
CampaignNotificationResource[]
required
data[].id
string
required
data[].name
string
required
data[].type
string
required
data[].headline
string
required
data[].body
string
required
data[].media
object | null
data[].media.id
string
required
data[].media.url
string | null
required
data[].media.type
string
required
data[].cta_label
string | null
required
data[].cta_url
string | null
required
data[].questions
NotificationQuestionResource[]
data[].questions[].id
string
required
data[].questions[].question_text
string
required
data[].questions[].type
string
required
data[].questions[].required
boolean
required
data[].questions[].choices
string[]
data[].trigger_type
string | null
required
data[].trigger_config
string | null
required
data[].starts_at
string | null
required
data[].ends_at
string | null
required
data[].created_at
string | null
required
data[].updated_at
string | null
required
401

The token is missing, expired or revoked.

unauthenticated
401 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
403

The token can't do this.

missing_abilityplatform_api_not_enabledwrong_environment
403 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
404

Nothing with that ID in this workspace.

not_found
404 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
429

Too many requests. Wait for the number of seconds in Retry-After.

rate_limited
429 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
Request
curl 'https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications' \
  -H 'Accept: application/json'
Request
const response = await fetch('https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications', {
  method: 'GET',
  headers: {
    Accept: 'application/json',
  },
});

const data = await response.json();
Request
import requests

response = requests.request(
    'GET',
    'https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications',
    headers={
        'Accept': 'application/json',
    },
)
response.raise_for_status()
data = response.json()
Request
<?php

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications', [
    'headers' => [
        'Accept' => 'application/json',
    ],
]);

$data = json_decode((string) $response->getBody(), true);

Add a notification to a campaign#

POST/campaigns/{campaign}/notifications
Needs notifications:writeNo token needed

Creates the notification's content and attaches it with a trigger in one call. A push campaign's notification needs trigger_type: fixed_time and trigger_config.send_at (a time on the campaign's day, e.g. "10:00"); a geo campaign's needs trigger_type: on_enter or on_exit, with an optional starts_at/ ends_at window inside the campaign's own. A survey needs 1–5 questions (OPEN_ENDED, SINGLE_CHOICE, MULTIPLE_CHOICE, RATING, BOOLEAN, NUMBER or SLIDER; choice types need 2–4 choices) and carries no media.

Path parameters

campaign
string (uuid)
required

The campaign ID

Request body

application/jsonStoreCampaignNotificationRequestrequired
name
string
required
type
string
required
One of: message, survey
headline
string
required
body
string
required
media_asset_id
string (uuid) | null
cta_label
string | null
cta_url
string (uri) | null
questions
object[]
required
questions[].id
string (uuid) | null
questions[].question_text
string
required
questions[].type
string
required
One of: OPEN_ENDED, SINGLE_CHOICE, MULTIPLE_CHOICE, RATING, BOOLEAN, NUMBER, SLIDER
questions[].required
boolean
questions[].choices
object[]
questions[].choices[].id
string (uuid) | null
questions[].choices[].text
string
required
trigger_type
string
required
One of: on_enter, on_exit
trigger_config
object
trigger_config.send_at
string | null
starts_at
string | null
ends_at
string | null

Responses

201
201 response
{
  "data": {
    "id": "0192f0a4-6b1e-7c3a-9d2f-5e8b1a4d5001",
    "campaign_id": "0192f0a4-6b1e-7c3a-9d2f-5e8b1a4d4001",
    "headline": "Coffee's on us ☕",
    "body": "Show this at the counter for a free flat white before 11am.",
    "media_asset_id": null,
    "cta_label": "Get the voucher",
    "cta_url": "https://harbour.coffee/free",
    "trigger_type": "on_enter",
    "created_at": "2026-09-20T09:12:00Z"
  }
}
Response fields
data
CampaignNotificationResource
required
data.id
string
required
data.name
string
required
data.type
string
required
data.headline
string
required
data.body
string
required
data.media
object | null
data.media.id
string
required
data.media.url
string | null
required
data.media.type
string
required
data.cta_label
string | null
required
data.cta_url
string | null
required
data.questions
NotificationQuestionResource[]
data.questions[].id
string
required
data.questions[].question_text
string
required
data.questions[].type
string
required
data.questions[].required
boolean
required
data.questions[].choices
string[]
data.trigger_type
string | null
required
data.trigger_config
string | null
required
data.starts_at
string | null
required
data.ends_at
string | null
required
data.created_at
string | null
required
data.updated_at
string | null
required
401

The token is missing, expired or revoked.

unauthenticated
401 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
403

The token can't do this.

missing_abilityplatform_api_not_enabledwrong_environment
403 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
404

Nothing with that ID in this workspace.

not_found
404 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
422

Some fields aren't valid. fields says which, with the dashboard's wording.

validation_failed
422 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
429

Too many requests. Wait for the number of seconds in Retry-After.

rate_limited
429 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
Request
curl -X POST 'https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  --data '{
  "name": "Morning flat white offer",
  "type": "message",
  "headline": "Coffee'\''s on us ☕",
  "body": "Show this at the counter for a free flat white before 11am.",
  "trigger_type": "on_enter",
  "cta_label": "Get the voucher",
  "cta_url": "https://harbour.coffee/free"
}'
Request
const response = await fetch('https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications', {
  method: 'POST',
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Morning flat white offer',
    type: 'message',
    headline: 'Coffee\'s on us ☕',
    body: 'Show this at the counter for a free flat white before 11am.',
    trigger_type: 'on_enter',
    cta_label: 'Get the voucher',
    cta_url: 'https://harbour.coffee/free',
  }),
});

const data = await response.json();
Request
import requests

response = requests.request(
    'POST',
    'https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications',
    headers={
        'Accept': 'application/json',
        'Content-Type': 'application/json',
    },
    json={
        'name': 'Morning flat white offer',
        'type': 'message',
        'headline': 'Coffee\'s on us ☕',
        'body': 'Show this at the counter for a free flat white before 11am.',
        'trigger_type': 'on_enter',
        'cta_label': 'Get the voucher',
        'cta_url': 'https://harbour.coffee/free',
    },
)
response.raise_for_status()
data = response.json()
Request
<?php

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications', [
    'headers' => [
        'Accept' => 'application/json',
        'Content-Type' => 'application/json',
    ],
    'json' => [
        'name' => 'Morning flat white offer',
        'type' => 'message',
        'headline' => 'Coffee\'s on us ☕',
        'body' => 'Show this at the counter for a free flat white before 11am.',
        'trigger_type' => 'on_enter',
        'cta_label' => 'Get the voucher',
        'cta_url' => 'https://harbour.coffee/free',
    ],
]);

$data = json_decode((string) $response->getBody(), true);

Get a campaign notification#

GET/campaigns/{campaign}/notifications/{notification}
Needs notifications:readNo token needed

Path parameters

campaign
string (uuid)
required

The campaign ID

notification
string (uuid)
required

The notification ID

Responses

200
200 response
{
  "data": {
    "id": "0192f0a4-6b1e-7c3a-9d2f-5e8b1a4d5001",
    "campaign_id": "0192f0a4-6b1e-7c3a-9d2f-5e8b1a4d4001",
    "headline": "Coffee's on us ☕",
    "body": "Show this at the counter for a free flat white before 11am.",
    "media_asset_id": null,
    "cta_label": "Get the voucher",
    "cta_url": "https://harbour.coffee/free",
    "trigger_type": "on_enter",
    "created_at": "2026-09-20T09:12:00Z"
  }
}
Response fields
data
CampaignNotificationResource
required
data.id
string
required
data.name
string
required
data.type
string
required
data.headline
string
required
data.body
string
required
data.media
object | null
data.media.id
string
required
data.media.url
string | null
required
data.media.type
string
required
data.cta_label
string | null
required
data.cta_url
string | null
required
data.questions
NotificationQuestionResource[]
data.questions[].id
string
required
data.questions[].question_text
string
required
data.questions[].type
string
required
data.questions[].required
boolean
required
data.questions[].choices
string[]
data.trigger_type
string | null
required
data.trigger_config
string | null
required
data.starts_at
string | null
required
data.ends_at
string | null
required
data.created_at
string | null
required
data.updated_at
string | null
required
401

The token is missing, expired or revoked.

unauthenticated
401 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
403

The token can't do this.

missing_abilityplatform_api_not_enabledwrong_environment
403 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
404

Nothing with that ID in this workspace.

not_found
404 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
429

Too many requests. Wait for the number of seconds in Retry-After.

rate_limited
429 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
Request
curl 'https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications/{notification}' \
  -H 'Accept: application/json'
Request
const response = await fetch('https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications/{notification}', {
  method: 'GET',
  headers: {
    Accept: 'application/json',
  },
});

const data = await response.json();
Request
import requests

response = requests.request(
    'GET',
    'https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications/{notification}',
    headers={
        'Accept': 'application/json',
    },
)
response.raise_for_status()
data = response.json()
Request
<?php

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications/{notification}', [
    'headers' => [
        'Accept' => 'application/json',
    ],
]);

$data = json_decode((string) $response->getBody(), true);

Update a campaign notification#

PATCH/campaigns/{campaign}/notifications/{notification}
Needs notifications:writeNo token needed

The whole notification (content and trigger) is resubmitted, same rules as adding one.

Path parameters

campaign
string (uuid)
required

The campaign ID

notification
string (uuid)
required

The notification ID

Request body

application/jsonUpdateCampaignNotificationRequestrequired
name
string
required
type
string
required
One of: message, survey
headline
string
required
body
string
required
media_asset_id
string (uuid) | null
cta_label
string | null
cta_url
string (uri) | null
questions
object[]
required
questions[].id
string (uuid) | null
questions[].question_text
string
required
questions[].type
string
required
One of: OPEN_ENDED, SINGLE_CHOICE, MULTIPLE_CHOICE, RATING, BOOLEAN, NUMBER, SLIDER
questions[].required
boolean
questions[].choices
object[]
questions[].choices[].id
string (uuid) | null
questions[].choices[].text
string
required
trigger_type
string
required
One of: on_enter, on_exit
trigger_config
object
trigger_config.send_at
string | null
starts_at
string | null
ends_at
string | null

Responses

200
200 response
{
  "data": {
    "id": "0192f0a4-6b1e-7c3a-9d2f-5e8b1a4d5001",
    "campaign_id": "0192f0a4-6b1e-7c3a-9d2f-5e8b1a4d4001",
    "headline": "Coffee's on us ☕",
    "body": "Show this at the counter for a free flat white before 11am.",
    "media_asset_id": null,
    "cta_label": "Get the voucher",
    "cta_url": "https://harbour.coffee/free",
    "trigger_type": "on_enter",
    "created_at": "2026-09-20T09:12:00Z"
  }
}
Response fields
data
CampaignNotificationResource
required
data.id
string
required
data.name
string
required
data.type
string
required
data.headline
string
required
data.body
string
required
data.media
object | null
data.media.id
string
required
data.media.url
string | null
required
data.media.type
string
required
data.cta_label
string | null
required
data.cta_url
string | null
required
data.questions
NotificationQuestionResource[]
data.questions[].id
string
required
data.questions[].question_text
string
required
data.questions[].type
string
required
data.questions[].required
boolean
required
data.questions[].choices
string[]
data.trigger_type
string | null
required
data.trigger_config
string | null
required
data.starts_at
string | null
required
data.ends_at
string | null
required
data.created_at
string | null
required
data.updated_at
string | null
required
401

The token is missing, expired or revoked.

unauthenticated
401 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
403

The token can't do this.

missing_abilityplatform_api_not_enabledwrong_environment
403 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
404

Nothing with that ID in this workspace.

not_found
404 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
422

Some fields aren't valid. fields says which, with the dashboard's wording.

validation_failed
422 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
429

Too many requests. Wait for the number of seconds in Retry-After.

rate_limited
429 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
Request
curl -X PATCH 'https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications/{notification}' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  --data '{
  "headline": "Two coffees for one"
}'
Request
const response = await fetch('https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications/{notification}', {
  method: 'PATCH',
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    headline: 'Two coffees for one',
  }),
});

const data = await response.json();
Request
import requests

response = requests.request(
    'PATCH',
    'https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications/{notification}',
    headers={
        'Accept': 'application/json',
        'Content-Type': 'application/json',
    },
    json={
        'headline': 'Two coffees for one',
    },
)
response.raise_for_status()
data = response.json()
Request
<?php

$client = new \GuzzleHttp\Client();

$response = $client->request('PATCH', 'https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications/{notification}', [
    'headers' => [
        'Accept' => 'application/json',
        'Content-Type' => 'application/json',
    ],
    'json' => [
        'headline' => 'Two coffees for one',
    ],
]);

$data = json_decode((string) $response->getBody(), true);

Remove a notification from a campaign#

DELETE/campaigns/{campaign}/notifications/{notification}
Needs notifications:writeNo token needed

Detaches it; its content is also deleted, but only once it's no longer attached to any other campaign.

Path parameters

campaign
string (uuid)
required

The campaign ID

notification
string (uuid)
required

The notification ID

Responses

200
200 response
{
  "data": {
    "id": "0192f0a4-6b1e-7c3a-9d2f-5e8b1a4d5001",
    "deleted": true
  }
}
Response fields
data
object
required
data.id
string
required
data.removed
boolean
required
401

The token is missing, expired or revoked.

unauthenticated
401 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
403

The token can't do this.

missing_abilityplatform_api_not_enabledwrong_environment
403 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
404

Nothing with that ID in this workspace.

not_found
404 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
429

Too many requests. Wait for the number of seconds in Retry-After.

rate_limited
429 response
{
  "error": {
    "code": "string",
    "message": "string",
    "fields": {}
  }
}
Request
curl -X DELETE 'https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications/{notification}' \
  -H 'Accept: application/json'
Request
const response = await fetch('https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications/{notification}', {
  method: 'DELETE',
  headers: {
    Accept: 'application/json',
  },
});

const data = await response.json();
Request
import requests

response = requests.request(
    'DELETE',
    'https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications/{notification}',
    headers={
        'Accept': 'application/json',
    },
)
response.raise_for_status()
data = response.json()
Request
<?php

$client = new \GuzzleHttp\Client();

$response = $client->request('DELETE', 'https://api.bubbl.tech/platform/v1/campaigns/{campaign}/notifications/{notification}', [
    'headers' => [
        'Accept' => 'application/json',
    ],
]);

$data = json_decode((string) $response->getBody(), true);