Locations
5 endpointsPlaces a campaign can target — a circle or a polygon. A location is only ever created by importing a GeoJSON FeatureCollection (see the import endpoint); this resource lists, reads, updates and deletes ones already imported.
List locations#
/locationsNewest first. Each comes back with its shape as GeoJSON — a Point with a radius_meters for a circle, a Polygon otherwise — and the names of any location groups it belongs to.
Query parameters
An opaque value from a previous page's meta.next_cursor. Omit for the first page.
How many to return, up to 100.
20Only locations updated at or after this ISO 8601 timestamp.
Responses
200
{
"data": [
{
"id": "0192f0a4-6b1e-7c3a-9d2f-5e8b1a4d3001",
"external_id": "store-014",
"name": "Harbour Coffee, Quay Street",
"address": "12 Quay Street, Bristol BS1 4DB",
"type": "circle",
"geometry": {
"type": "Point",
"coordinates": [
-2.5879,
51.4545
]
},
"radius_meters": 150,
"groups": [
"Bristol stores"
],
"created_at": "2026-09-20T09:12:00+00:00",
"updated_at": "2026-09-27T14:03:00+00:00"
}
],
"meta": {
"next_cursor": null
}
}Response fields
401The token is missing, unknown, revoked or expired.
missing_tokeninvalid_tokentoken_revokedtoken_expired
missing_tokenMissing bearer token.
invalid_tokenUnknown API token. Check it was copied in full, and that it is for this environment.
token_revokedThe token "Zapier" was revoked on 2026-10-06 at 18:27 UTC. Issue a new one from Configuration › Platform API.
token_expiredThis token has expired. Issue a new one from Configuration › Platform API.
{
"error": {
"code": "missing_token",
"message": "Missing bearer token."
}
}403The token or workspace can't do this.
missing_abilityplatform_api_not_enabledwrong_environmentworkspace_paused
missing_abilityThis token doesn't have the campaigns:write scope.
platform_api_not_enabledThe full Platform API isn't enabled for this workspace yet. Contact customer services to turn it on.
wrong_environmentThis is a Sandbox token: use the Sandbox API address. https://api.sandbox.bubbl.tech/platform/v1
workspace_pausedThis workspace is scheduled for deletion; its API access is paused.
{
"error": {
"code": "missing_ability",
"message": "This token doesn't have the campaigns:write scope."
}
}422Something in the request isn't valid. fields says what, with the dashboard's wording.
invalid_parameterinvalid_cursor
fields says what, with the dashboard's wording.invalid_parameterupdated_since must be a valid date.
invalid_cursorThat cursor is invalid or has expired.
{
"error": {
"code": "invalid_parameter",
"message": "updated_since must be a valid date.",
"fields": {
"updated_since": [
"Must be a valid date."
]
}
}
}429Too many requests. Wait for the number of seconds in Retry-After.
rate_limitedtoo_many_failed_attempts
Retry-After.rate_limitedToo many requests. Slow down and try again shortly.
too_many_failed_attemptsToo many failed attempts. Try again shortly.
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Slow down and try again shortly."
}
}curl 'https://api.bubbl.tech/platform/v1/locations?limit=20' \
-H 'Authorization: Bearer bubbl_YOUR_TOKEN' \
-H 'Accept: application/json'const response = await fetch('https://api.bubbl.tech/platform/v1/locations?limit=20', {
method: 'GET',
headers: {
Authorization: 'Bearer bubbl_YOUR_TOKEN',
Accept: 'application/json',
},
});
const data = await response.json();import requests
response = requests.request(
'GET',
'https://api.bubbl.tech/platform/v1/locations?limit=20',
headers={
'Authorization': 'Bearer bubbl_YOUR_TOKEN',
'Accept': 'application/json',
},
)
response.raise_for_status()
data = response.json()<?php
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.bubbl.tech/platform/v1/locations?limit=20', [
'headers' => [
'Authorization' => 'Bearer bubbl_YOUR_TOKEN',
'Accept' => 'application/json',
],
]);
$data = json_decode((string) $response->getBody(), true);Get a location#
/locations/{location}Path parameters
The location ID
Responses
200
{
"data": {
"id": "0192f0a4-6b1e-7c3a-9d2f-5e8b1a4d3001",
"external_id": "store-014",
"name": "Harbour Coffee, Quay Street",
"address": "12 Quay Street, Bristol BS1 4DB",
"type": "circle",
"geometry": {
"type": "Point",
"coordinates": [
-2.5879,
51.4545
]
},
"radius_meters": 150,
"groups": [
"Bristol stores"
],
"created_at": "2026-09-20T09:12:00+00:00",
"updated_at": "2026-09-27T14:03:00+00:00"
}
}Response fields
401The token is missing, unknown, revoked or expired.
missing_tokeninvalid_tokentoken_revokedtoken_expired
missing_tokenMissing bearer token.
invalid_tokenUnknown API token. Check it was copied in full, and that it is for this environment.
token_revokedThe token "Zapier" was revoked on 2026-10-06 at 18:27 UTC. Issue a new one from Configuration › Platform API.
token_expiredThis token has expired. Issue a new one from Configuration › Platform API.
{
"error": {
"code": "missing_token",
"message": "Missing bearer token."
}
}403The token or workspace can't do this.
missing_abilityplatform_api_not_enabledwrong_environmentworkspace_paused
missing_abilityThis token doesn't have the campaigns:write scope.
platform_api_not_enabledThe full Platform API isn't enabled for this workspace yet. Contact customer services to turn it on.
wrong_environmentThis is a Sandbox token: use the Sandbox API address. https://api.sandbox.bubbl.tech/platform/v1
workspace_pausedThis workspace is scheduled for deletion; its API access is paused.
{
"error": {
"code": "missing_ability",
"message": "This token doesn't have the campaigns:write scope."
}
}404Nothing with that ID in this workspace.
not_found
not_foundNo such resource in this workspace.
{
"error": {
"code": "not_found",
"message": "No such resource in this workspace."
}
}429Too many requests. Wait for the number of seconds in Retry-After.
rate_limitedtoo_many_failed_attempts
Retry-After.rate_limitedToo many requests. Slow down and try again shortly.
too_many_failed_attemptsToo many failed attempts. Try again shortly.
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Slow down and try again shortly."
}
}curl 'https://api.bubbl.tech/platform/v1/locations/{location}' \
-H 'Authorization: Bearer bubbl_YOUR_TOKEN' \
-H 'Accept: application/json'const response = await fetch('https://api.bubbl.tech/platform/v1/locations/{location}', {
method: 'GET',
headers: {
Authorization: 'Bearer bubbl_YOUR_TOKEN',
Accept: 'application/json',
},
});
const data = await response.json();import requests
response = requests.request(
'GET',
'https://api.bubbl.tech/platform/v1/locations/{location}',
headers={
'Authorization': 'Bearer bubbl_YOUR_TOKEN',
'Accept': 'application/json',
},
)
response.raise_for_status()
data = response.json()<?php
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.bubbl.tech/platform/v1/locations/{location}', [
'headers' => [
'Authorization' => 'Bearer bubbl_YOUR_TOKEN',
'Accept' => 'application/json',
],
]);
$data = json_decode((string) $response->getBody(), true);Update a location#
/locations/{location}Only its name, address and external_id — not its shape. To change a location's shape, re-import it with the same properties.external_id (see the import endpoint).
Path parameters
The location ID
Headers
A unique value (e.g. a UUID) for this change. A retry with the same key within 24 hours gets the first response back, with Idempotent-Replayed: true, instead of making the change again.
Request body
application/jsonUpdateLocationRequestResponses
200
{
"data": {
"id": "0192f0a4-6b1e-7c3a-9d2f-5e8b1a4d3001",
"external_id": "store-014",
"name": "Harbour Coffee, Quay Street",
"address": "12 Quay Street, Bristol BS1 4DB",
"type": "circle",
"geometry": {
"type": "Point",
"coordinates": [
-2.5879,
51.4545
]
},
"radius_meters": 150,
"groups": [
"Bristol stores"
],
"created_at": "2026-09-20T09:12:00+00:00",
"updated_at": "2026-09-27T14:03:00+00:00"
}
}Response fields
401The token is missing, unknown, revoked or expired.
missing_tokeninvalid_tokentoken_revokedtoken_expired
missing_tokenMissing bearer token.
invalid_tokenUnknown API token. Check it was copied in full, and that it is for this environment.
token_revokedThe token "Zapier" was revoked on 2026-10-06 at 18:27 UTC. Issue a new one from Configuration › Platform API.
token_expiredThis token has expired. Issue a new one from Configuration › Platform API.
{
"error": {
"code": "missing_token",
"message": "Missing bearer token."
}
}403The token or workspace can't do this.
missing_abilityplatform_api_not_enabledwrong_environmentworkspace_paused
missing_abilityThis token doesn't have the campaigns:write scope.
platform_api_not_enabledThe full Platform API isn't enabled for this workspace yet. Contact customer services to turn it on.
wrong_environmentThis is a Sandbox token: use the Sandbox API address. https://api.sandbox.bubbl.tech/platform/v1
workspace_pausedThis workspace is scheduled for deletion; its API access is paused.
{
"error": {
"code": "missing_ability",
"message": "This token doesn't have the campaigns:write scope."
}
}404Nothing with that ID in this workspace.
not_found
not_foundNo such resource in this workspace.
{
"error": {
"code": "not_found",
"message": "No such resource in this workspace."
}
}409A request with the same Idempotency-Key is still running (retry after Retry-After seconds), or the change conflicts with something that depends on it.
request_in_progress
Idempotency-Key is still running (retry after Retry-After seconds), or the change conflicts with something that depends on it.request_in_progressThis request is still being handled. Try again shortly.
{
"error": {
"code": "request_in_progress",
"message": "This request is still being handled. Try again shortly."
}
}422Something in the request isn't valid. fields says what, with the dashboard's wording.
validation_failed
fields says what, with the dashboard's wording.validation_failedThe given data was invalid.
{
"error": {
"code": "validation_failed",
"message": "The given data was invalid.",
"fields": {
"name": [
"The name field is required."
]
}
}
}429Too many requests. Wait for the number of seconds in Retry-After.
rate_limitedtoo_many_failed_attempts
Retry-After.rate_limitedToo many requests. Slow down and try again shortly.
too_many_failed_attemptsToo many failed attempts. Try again shortly.
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Slow down and try again shortly."
}
}curl -X PATCH 'https://api.bubbl.tech/platform/v1/locations/{location}' \
-H 'Authorization: Bearer bubbl_YOUR_TOKEN' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
--data '{
"name": "Harbour Coffee, Quay Street",
"address": "12 Quay Street, Bristol BS1 4DB",
"external_id": "store-014"
}'const response = await fetch('https://api.bubbl.tech/platform/v1/locations/{location}', {
method: 'PATCH',
headers: {
Authorization: 'Bearer bubbl_YOUR_TOKEN',
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Harbour Coffee, Quay Street',
address: '12 Quay Street, Bristol BS1 4DB',
external_id: 'store-014',
}),
});
const data = await response.json();import requests
response = requests.request(
'PATCH',
'https://api.bubbl.tech/platform/v1/locations/{location}',
headers={
'Authorization': 'Bearer bubbl_YOUR_TOKEN',
'Accept': 'application/json',
'Content-Type': 'application/json',
},
json={
'name': 'Harbour Coffee, Quay Street',
'address': '12 Quay Street, Bristol BS1 4DB',
'external_id': 'store-014',
},
)
response.raise_for_status()
data = response.json()<?php
$client = new \GuzzleHttp\Client();
$response = $client->request('PATCH', 'https://api.bubbl.tech/platform/v1/locations/{location}', [
'headers' => [
'Authorization' => 'Bearer bubbl_YOUR_TOKEN',
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
'json' => [
'name' => 'Harbour Coffee, Quay Street',
'address' => '12 Quay Street, Bristol BS1 4DB',
'external_id' => 'store-014',
],
]);
$data = json_decode((string) $response->getBody(), true);Delete a location#
/locations/{location}Path parameters
The location ID
Headers
A unique value (e.g. a UUID) for this change. A retry with the same key within 24 hours gets the first response back, with Idempotent-Replayed: true, instead of making the change again.
Responses
200
{
"data": {
"id": "0192f0a4-6b1e-7c3a-9d2f-5e8b1a4d3001",
"deleted": true
}
}Response fields
401The token is missing, unknown, revoked or expired.
missing_tokeninvalid_tokentoken_revokedtoken_expired
missing_tokenMissing bearer token.
invalid_tokenUnknown API token. Check it was copied in full, and that it is for this environment.
token_revokedThe token "Zapier" was revoked on 2026-10-06 at 18:27 UTC. Issue a new one from Configuration › Platform API.
token_expiredThis token has expired. Issue a new one from Configuration › Platform API.
{
"error": {
"code": "missing_token",
"message": "Missing bearer token."
}
}403The token or workspace can't do this.
missing_abilityplatform_api_not_enabledwrong_environmentworkspace_paused
missing_abilityThis token doesn't have the campaigns:write scope.
platform_api_not_enabledThe full Platform API isn't enabled for this workspace yet. Contact customer services to turn it on.
wrong_environmentThis is a Sandbox token: use the Sandbox API address. https://api.sandbox.bubbl.tech/platform/v1
workspace_pausedThis workspace is scheduled for deletion; its API access is paused.
{
"error": {
"code": "missing_ability",
"message": "This token doesn't have the campaigns:write scope."
}
}404Nothing with that ID in this workspace.
not_found
not_foundNo such resource in this workspace.
{
"error": {
"code": "not_found",
"message": "No such resource in this workspace."
}
}409A request with the same Idempotency-Key is still running (retry after Retry-After seconds), or the change conflicts with something that depends on it.
request_in_progress
Idempotency-Key is still running (retry after Retry-After seconds), or the change conflicts with something that depends on it.request_in_progressThis request is still being handled. Try again shortly.
{
"error": {
"code": "request_in_progress",
"message": "This request is still being handled. Try again shortly."
}
}429Too many requests. Wait for the number of seconds in Retry-After.
rate_limitedtoo_many_failed_attempts
Retry-After.rate_limitedToo many requests. Slow down and try again shortly.
too_many_failed_attemptsToo many failed attempts. Try again shortly.
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Slow down and try again shortly."
}
}curl -X DELETE 'https://api.bubbl.tech/platform/v1/locations/{location}' \
-H 'Authorization: Bearer bubbl_YOUR_TOKEN' \
-H 'Accept: application/json'const response = await fetch('https://api.bubbl.tech/platform/v1/locations/{location}', {
method: 'DELETE',
headers: {
Authorization: 'Bearer bubbl_YOUR_TOKEN',
Accept: 'application/json',
},
});
const data = await response.json();import requests
response = requests.request(
'DELETE',
'https://api.bubbl.tech/platform/v1/locations/{location}',
headers={
'Authorization': 'Bearer bubbl_YOUR_TOKEN',
'Accept': 'application/json',
},
)
response.raise_for_status()
data = response.json()<?php
$client = new \GuzzleHttp\Client();
$response = $client->request('DELETE', 'https://api.bubbl.tech/platform/v1/locations/{location}', [
'headers' => [
'Authorization' => 'Bearer bubbl_YOUR_TOKEN',
'Accept' => 'application/json',
],
]);
$data = json_decode((string) $response->getBody(), true);Import locations#
/locations/importA GeoJSON FeatureCollection. Each feature needs properties.name; a Point geometry also needs properties.radius (metres, 50–1000) and becomes a circle, a Polygon becomes a polygon. properties.external_id makes a re-import update the same location instead of creating a new one; properties.group adds it to a location group (created if it doesn't exist).
The response gives one result per feature — created, updated, skipped (over the plan's location limit) or failed, with reasons for the latter two. Send ?dry_run=1 to see what would happen without writing anything, and an Idempotency-Key header so a retried request after a lost response doesn't import the same features twice.
Location import must be in the company's plan (it isn't on Free): otherwise 403 feature_not_in_plan, dry runs included.
Headers
A unique value (e.g. a UUID) for this change. A retry with the same key within 24 hours gets the first response back, with Idempotent-Replayed: true, instead of making the change again.
Request body
application/jsonImportLocationsRequestrequiredFeatureCollection1 to 1,000 GeoJSON Features. A feature that isn't valid fails on its own (see the response's results); it doesn't fail the request.
FeaturePoint (with properties.radius) becomes a circle; Polygon becomes a polygon.
Point, Polygon[longitude, latitude] for a Point; one ring of 3–99 [longitude, latitude] points for a Polygon.
The location's name, up to 255 characters.
A Point's radius in metres, 50–1000.
Your own id. A re-import with the same one updates that location instead of creating another.
A location group to add it to, created if it doesn't exist.
Responses
200
{
"data": {
"dry_run": false,
"results": [
{
"index": 0,
"status": "created",
"name": "Riverside Store",
"id": "0192f0a4-6b1e-7c3a-9d2f-5e8b1a4d2001",
"reasons": null
},
{
"index": 1,
"status": "failed",
"name": null,
"reasons": [
"Name is missing (properties.name)"
]
}
],
"summary": {
"created": 1,
"updated": 0,
"skipped": 0,
"failed": 1
}
}
}Response fields
updated, created401The token is missing, unknown, revoked or expired.
missing_tokeninvalid_tokentoken_revokedtoken_expired
missing_tokenMissing bearer token.
invalid_tokenUnknown API token. Check it was copied in full, and that it is for this environment.
token_revokedThe token "Zapier" was revoked on 2026-10-06 at 18:27 UTC. Issue a new one from Configuration › Platform API.
token_expiredThis token has expired. Issue a new one from Configuration › Platform API.
{
"error": {
"code": "missing_token",
"message": "Missing bearer token."
}
}403The token or workspace can't do this.
missing_abilityplatform_api_not_enabledwrong_environmentworkspace_paused
missing_abilityThis token doesn't have the campaigns:write scope.
platform_api_not_enabledThe full Platform API isn't enabled for this workspace yet. Contact customer services to turn it on.
wrong_environmentThis is a Sandbox token: use the Sandbox API address. https://api.sandbox.bubbl.tech/platform/v1
workspace_pausedThis workspace is scheduled for deletion; its API access is paused.
{
"error": {
"code": "missing_ability",
"message": "This token doesn't have the campaigns:write scope."
}
}409A request with the same Idempotency-Key is still running (retry after Retry-After seconds), or the change conflicts with something that depends on it.
request_in_progress
Idempotency-Key is still running (retry after Retry-After seconds), or the change conflicts with something that depends on it.request_in_progressThis request is still being handled. Try again shortly.
{
"error": {
"code": "request_in_progress",
"message": "This request is still being handled. Try again shortly."
}
}422Something in the request isn't valid. fields says what, with the dashboard's wording.
validation_failed
fields says what, with the dashboard's wording.validation_failedThe given data was invalid.
{
"error": {
"code": "validation_failed",
"message": "The given data was invalid.",
"fields": {
"name": [
"The name field is required."
]
}
}
}429Too many requests. Wait for the number of seconds in Retry-After.
rate_limitedtoo_many_failed_attempts
Retry-After.rate_limitedToo many requests. Slow down and try again shortly.
too_many_failed_attemptsToo many failed attempts. Try again shortly.
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Slow down and try again shortly."
}
}curl -X POST 'https://api.bubbl.tech/platform/v1/locations/import' \
-H 'Authorization: Bearer bubbl_YOUR_TOKEN' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
--data '{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-2.5879,
51.4545
]
},
"properties": {
"name": "Harbour Coffee, Quay Street",
"radius": 150,
"external_id": "store-014",
"group": "Bristol stores"
}
}
]
}'const response = await fetch('https://api.bubbl.tech/platform/v1/locations/import', {
method: 'POST',
headers: {
Authorization: 'Bearer bubbl_YOUR_TOKEN',
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [
-2.5879,
51.4545,
],
},
properties: {
name: 'Harbour Coffee, Quay Street',
radius: 150,
external_id: 'store-014',
group: 'Bristol stores',
},
},
],
}),
});
const data = await response.json();import requests
response = requests.request(
'POST',
'https://api.bubbl.tech/platform/v1/locations/import',
headers={
'Authorization': 'Bearer bubbl_YOUR_TOKEN',
'Accept': 'application/json',
'Content-Type': 'application/json',
},
json={
'type': 'FeatureCollection',
'features': [
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [
-2.5879,
51.4545,
],
},
'properties': {
'name': 'Harbour Coffee, Quay Street',
'radius': 150,
'external_id': 'store-014',
'group': 'Bristol stores',
},
},
],
},
)
response.raise_for_status()
data = response.json()<?php
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.bubbl.tech/platform/v1/locations/import', [
'headers' => [
'Authorization' => 'Bearer bubbl_YOUR_TOKEN',
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
'json' => [
'type' => 'FeatureCollection',
'features' => [
[
'type' => 'Feature',
'geometry' => [
'type' => 'Point',
'coordinates' => [
-2.5879,
51.4545,
],
],
'properties' => [
'name' => 'Harbour Coffee, Quay Street',
'radius' => 150,
'external_id' => 'store-014',
'group' => 'Bristol stores',
],
],
],
],
]);
$data = json_decode((string) $response->getBody(), true);