Advocates API
Read your advocates: their status, tags, public profile, and, with an extra permission, their contact details, address, and custom fields.
Requires the advocates permission. Personal data also requires advocates:pii. See Personal data.
| Endpoint | Returns |
|---|---|
GET /api/v1/advocates |
A paginated list of advocates |
GET /api/v1/advocates/:id |
One advocate |
List advocates
GET https://integrations.stokedhq.com/api/v1/advocates
With no filters, the list contains every advocate in your community: pending, active, and inactive, including advocates who have been deleted or whose personal data has been erased. See Deleted and erased advocates.
Parameters
| Parameter | Description |
|---|---|
filter[status] |
One or more of pending, active, inactive, separated by commas: filter[status]=pending,active |
filter[created_since] |
Only advocates created at or after this time |
filter[updated_since] |
Only advocates updated at or after this time. Use this for incremental syncing. |
page[size] |
Advocates per page. Defaults to 50, maximum 200. |
page[after] |
The cursor from a previous response’s links.next. See Pagination. |
page[number] |
A page to jump to. Defaults to 1. |
Time filters accept an ISO 8601 timestamp with a time zone (2026-09-01T00:00:00Z) or a date (2026-09-01, read as midnight UTC). Both are inclusive.
Any other parameter — including sort — returns a 400 error. Advocates always come back least recently updated first.
Request
cURL
curl -G "https://integrations.stokedhq.com/api/v1/advocates" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/vnd.api+json" \
--data-urlencode "filter[status]=active"
Ruby
require "net/http"
require "json"
uri = URI("https://integrations.stokedhq.com/api/v1/advocates")
uri.query = URI.encode_www_form("filter[status]" => "active")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer YOUR_API_KEY"
request["Accept"] = "application/vnd.api+json"
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
raise "HTTP #{response.code}: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
puts JSON.parse(response.body)["data"]
Python
import requests
response = requests.get(
"https://integrations.stokedhq.com/api/v1/advocates",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Accept": "application/vnd.api+json",
},
params={"filter[status]": "active"},
)
response.raise_for_status()
print(response.json()["data"])
C#
using System.Net.Http.Headers;
using System.Text.Json;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "YOUR_API_KEY");
client.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.api+json");
using var response = await client.GetAsync("https://integrations.stokedhq.com/api/v1/advocates?filter[status]=active");
response.EnsureSuccessStatusCode();
using var document = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
Console.WriteLine(document.RootElement.GetProperty("data"));
JavaScript
const url = new URL("https://integrations.stokedhq.com/api/v1/advocates");
url.searchParams.set("filter[status]", "active");
const response = await fetch(url, {
headers: {
Authorization: "Bearer YOUR_API_KEY",
Accept: "application/vnd.api+json",
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
const { data } = await response.json();
console.log(data);
Response
This key has both advocates and advocates:pii.
{
"data": [
{
"type": "advocates",
"id": "01k5exampleadv0cate0000001",
"attributes": {
"first_name": "Sarah",
"last_name": "Example",
"email": "sarah@example.com",
"phone_number": "+12025550100",
"handle": "sarah-e",
"profile_url": "https://example-bikes.stokedhq.com/sarah-e",
"status": "active",
"tags": [
"model/cargo-bike"
],
"headline": "Longtail owner, school-run veteran",
"description": "Ask me anything about carrying two kids and the groceries.",
"bio_link_title": null,
"bio_link_url": null,
"custom_pin_color": null,
"full_address_visible": false,
"display_name": "Sarah",
"display_location": "Springfield, IL",
"address": {
"address_1": "123 Example St",
"address_2": null,
"city": "Springfield",
"state": "IL",
"postal_code": "62701",
"country_code": "USA"
},
"latitude": 39.8,
"longitude": -89.65,
"custom_fields": {
"bike_model": "Longtail",
"accessories": [
"Rain cover",
"Child seats"
]
},
"visible_on_map": true,
"wallets": [
{
"id": "01k5examplewallet000000001",
"name": "Squad Points",
"unit_name": "points",
"currency_code": "PTS",
"balance": 125.0,
"lifetime": 300.0
}
],
"conversations_count": 1,
"created_at": "2026-08-20T14:00:00Z",
"updated_at": "2026-09-01T15:00:00Z",
"deleted_at": null
},
"relationships": {
"conversations": {
"links": {
"related": "https://integrations.stokedhq.com/api/v1/conversations?filter%5Badvocate%5D=01k5exampleadv0cate0000001"
}
}
},
"links": {
"self": "https://integrations.stokedhq.com/api/v1/advocates/01k5exampleadv0cate0000001"
},
"meta": {
"pii": true
}
}
],
"links": {
"self": "https://integrations.stokedhq.com/api/v1/advocates?filter%5Bstatus%5D=active&page%5Bnumber%5D=1&page%5Bsize%5D=50",
"first": "https://integrations.stokedhq.com/api/v1/advocates?filter%5Bstatus%5D=active&page%5Bnumber%5D=1&page%5Bsize%5D=50",
"prev": null,
"next": null,
"last": "https://integrations.stokedhq.com/api/v1/advocates?filter%5Bstatus%5D=active&page%5Bnumber%5D=1&page%5Bsize%5D=50"
},
"meta": {
"page": 1,
"per_page": 50,
"total_count": 1,
"total_pages": 1,
"custom_fields": [
{
"id": "01k5examplecust0mf1eld0001",
"identifier": "bike_model",
"label": "Bike model",
"response_type": "short_text",
"options": []
},
{
"id": "01k5examplecust0mf1eld0002",
"identifier": "accessories",
"label": "Accessories",
"response_type": "multiple_select",
"options": [
"Rain cover",
"Child seats",
"Front rack"
]
}
]
}
}
Get an advocate
GET https://integrations.stokedhq.com/api/v1/advocates/:id
Returns the same record as the list. An ID that doesn’t exist in your community returns 404.
The id is the same value a conversation gives you in relationships.advocate, and that relationship’s links.related is this URL.
Request
cURL
curl "https://integrations.stokedhq.com/api/v1/advocates/01k5exampleadv0cate0000001" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/vnd.api+json"
Ruby
require "net/http"
require "json"
uri = URI("https://integrations.stokedhq.com/api/v1/advocates/01k5exampleadv0cate0000001")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer YOUR_API_KEY"
request["Accept"] = "application/vnd.api+json"
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
raise "HTTP #{response.code}: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
puts JSON.parse(response.body)["data"]
Python
import requests
response = requests.get(
"https://integrations.stokedhq.com/api/v1/advocates/01k5exampleadv0cate0000001",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Accept": "application/vnd.api+json",
},
)
response.raise_for_status()
print(response.json()["data"])
C#
using System.Net.Http.Headers;
using System.Text.Json;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "YOUR_API_KEY");
client.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.api+json");
using var response = await client.GetAsync("https://integrations.stokedhq.com/api/v1/advocates/01k5exampleadv0cate0000001");
response.EnsureSuccessStatusCode();
using var document = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
Console.WriteLine(document.RootElement.GetProperty("data"));
JavaScript
const url = new URL("https://integrations.stokedhq.com/api/v1/advocates/01k5exampleadv0cate0000001");
const response = await fetch(url, {
headers: {
Authorization: "Bearer YOUR_API_KEY",
Accept: "application/vnd.api+json",
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
const { data } = await response.json();
console.log(data);
Response
{
"data": {
"type": "advocates",
"id": "01k5exampleadv0cate0000001",
"attributes": {
"first_name": "Sarah",
"last_name": "Example",
"email": "sarah@example.com",
"phone_number": "+12025550100",
"handle": "sarah-e",
"profile_url": "https://example-bikes.stokedhq.com/sarah-e",
"status": "active",
"tags": [
"model/cargo-bike"
],
"headline": "Longtail owner, school-run veteran",
"description": "Ask me anything about carrying two kids and the groceries.",
"bio_link_title": null,
"bio_link_url": null,
"custom_pin_color": null,
"full_address_visible": false,
"display_name": "Sarah",
"display_location": "Springfield, IL",
"address": {
"address_1": "123 Example St",
"address_2": null,
"city": "Springfield",
"state": "IL",
"postal_code": "62701",
"country_code": "USA"
},
"latitude": 39.8,
"longitude": -89.65,
"custom_fields": {
"bike_model": "Longtail",
"accessories": [
"Rain cover",
"Child seats"
]
},
"visible_on_map": true,
"wallets": [
{
"id": "01k5examplewallet000000001",
"name": "Squad Points",
"unit_name": "points",
"currency_code": "PTS",
"balance": 125.0,
"lifetime": 300.0
}
],
"conversations_count": 1,
"created_at": "2026-08-20T14:00:00Z",
"updated_at": "2026-09-01T15:00:00Z",
"deleted_at": null
},
"relationships": {
"conversations": {
"links": {
"related": "https://integrations.stokedhq.com/api/v1/conversations?filter%5Badvocate%5D=01k5exampleadv0cate0000001"
}
}
},
"links": {
"self": "https://integrations.stokedhq.com/api/v1/advocates/01k5exampleadv0cate0000001"
},
"meta": {
"pii": true
}
},
"meta": {
"custom_fields": [
{
"id": "01k5examplecust0mf1eld0001",
"identifier": "bike_model",
"label": "Bike model",
"response_type": "short_text",
"options": []
},
{
"id": "01k5examplecust0mf1eld0002",
"identifier": "accessories",
"label": "Accessories",
"response_type": "multiple_select",
"options": [
"Rain cover",
"Child seats",
"Front rack"
]
}
]
}
}
Personal data
Attributes marked advocates:pii below are only returned to a key that has the advocates:pii permission as well as advocates.
Without it, those attributes are left out of the response entirely rather than set to null, so null always means “no value” and never “not permitted”. Each record’s meta.pii tells you which kind of response you received.
The same advocate, read with a key that has only advocates:
{
"data": {
"type": "advocates",
"id": "01k5exampleadv0cate0000001",
"attributes": {
"handle": "sarah-e",
"profile_url": "https://example-bikes.stokedhq.com/sarah-e",
"status": "active",
"tags": [
"model/cargo-bike"
],
"headline": "Longtail owner, school-run veteran",
"description": "Ask me anything about carrying two kids and the groceries.",
"bio_link_title": null,
"bio_link_url": null,
"custom_pin_color": null,
"full_address_visible": false,
"visible_on_map": true,
"wallets": [
{
"id": "01k5examplewallet000000001",
"name": "Squad Points",
"unit_name": "points",
"currency_code": "PTS",
"balance": 125.0,
"lifetime": 300.0
}
],
"conversations_count": 1,
"created_at": "2026-08-20T14:00:00Z",
"updated_at": "2026-09-01T15:00:00Z",
"deleted_at": null
},
"relationships": {
"conversations": {
"links": {
"related": "https://integrations.stokedhq.com/api/v1/conversations?filter%5Badvocate%5D=01k5exampleadv0cate0000001"
}
}
},
"links": {
"self": "https://integrations.stokedhq.com/api/v1/advocates/01k5exampleadv0cate0000001"
},
"meta": {
"pii": false
}
},
"meta": {
"custom_fields": [
{
"id": "01k5examplecust0mf1eld0001",
"identifier": "bike_model",
"label": "Bike model",
"response_type": "short_text",
"options": []
},
{
"id": "01k5examplecust0mf1eld0002",
"identifier": "accessories",
"label": "Accessories",
"response_type": "multiple_select",
"options": [
"Rain cover",
"Child seats",
"Front rack"
]
}
]
}
}
headline and description are not gated: they are text the advocate wrote for their public profile, already shown on your community site. Note that an advocate is free to write their own name into either one.
handle and profile_url are not gated either, because the profile page is public. Handles are usually built from the advocate’s name and city (naomi-in-alexandria), so treat them as identifying. Use id, which is a random identifier, to match advocates against your own records.
Advocate attributes
Records with "type": "advocates".
| Attribute | Type | Requires | Description |
|---|---|---|---|
first_name |
string | advocates:pii |
|
last_name |
string or null | advocates:pii |
|
email |
string or null | advocates:pii |
|
phone_number |
string or null | advocates:pii |
In E.164 format, for example +12025550100 |
handle |
string | The advocate’s unique handle, the last part of their profile URL. Usually built from their name and city. | |
profile_url |
string or null | The advocate’s public profile on your community site. | |
status |
string | pending, active, or inactive |
|
tags |
array of strings | The advocate’s tags, in the order set in Settings. Each tag is given as its path: the tag’s group, a slash, then the tag’s name, for example model/q45. A tag that was never put in a group is in the general group, so it reads general/vip. |
|
headline |
string or null | The headline on their public profile | |
description |
string or null | The bio on their public profile | |
bio_link_title |
string or null | The label of the link on their public profile. When they gave a URL but no title, the URL without its scheme, as the profile shows it. | |
bio_link_url |
string or null | The link on their public profile | |
custom_pin_color |
string or null | The colour they chose for their map pin, as a CSS colour. null for the community default. |
|
full_address_visible |
boolean or null | Whether they chose to show their full address on their profile | |
display_name |
string | advocates:pii |
The name shown publicly: their custom display name, or their first name |
display_location |
string or null | advocates:pii |
The location shown publicly, for example “Springfield, IL” |
address |
object or null | advocates:pii |
The advocate’s address. null if none is on file. |
latitude |
number or null | advocates:pii |
Map coordinates of the address |
longitude |
number or null | advocates:pii |
|
custom_fields |
object | advocates:pii |
Your community’s custom fields. See The custom_fields object. |
visible_on_map |
boolean | Whether the advocate has chosen to appear on your community map | |
wallets |
array | The advocate’s reward wallets, with balances. See The wallets array. |
|
conversations_count |
integer | Number of conversations the advocate has been part of | |
created_at |
timestamp | When the advocate was added | |
updated_at |
timestamp | When the advocate last changed. See What changes updated_at. |
|
deleted_at |
timestamp or null | When the advocate was removed from your community. null for current advocates. Removed advocates stay in the API so your copy can mark them, and removing one updates updated_at. |
The address object
| Field | Type | Description |
|---|---|---|
address_1 |
string or null | Street address |
address_2 |
string or null | Apartment, suite, or unit |
city |
string or null | |
state |
string or null | State, province, or region |
postal_code |
string or null | |
country_code |
string or null | Three-letter ISO 3166-1 country code, for example USA or CAN |
The custom_fields object
One key per custom field defined in Settings > Advocate Custom Fields, named by the field’s identifier (the same name the CSV export uses after custom_field_). Every field is present for every advocate; the value is null when the advocate hasn’t answered.
Values are strings, formatted as they are in the CSV export (a checkbox is "Yes" or "No"), except multiple-select fields, which are arrays of the selected options — an empty array when unanswered.
To render a value you need the field’s type and options. Every advocate response carries them in its top-level meta.custom_fields: an array of {id, identifier, label, response_type, options}, one per field, in Settings order. The Custom Fields API has the full definitions.
The wallets array
One entry per reward wallet the advocate holds, in the order the wallets appear under Settings > Wallet Configurations. Most communities have a single points wallet, so the array has one entry. It is empty for an advocate who has no wallet yet, such as one who has not been activated.
| Field | Type | Description |
|---|---|---|
id |
string | The wallet’s ID. The same for every advocate who holds this wallet, so it is safe to group on. |
name |
string | The wallet’s name, for example Squad Points |
unit_name |
string | What one unit is called, for example points |
currency_code |
string | PTS for a points wallet, or an ISO 4217 code (USD, CAD) for a cash wallet |
balance |
number | What the advocate can spend now, in whole units: 125.0 is 125 points, or $125.00 in a cash wallet |
lifetime |
number | Everything the advocate has ever earned in this wallet, in the same units. Spending lowers balance but not lifetime. |
These are the same figures as the wallet_1_balance_raw and wallet_1_lifetime_raw columns of the advocates CSV export.
Relationships
| Relationship | Description |
|---|---|
conversations |
links.related is the conversations list filtered to this advocate (filter[advocate]). Reading it requires the conversations permission. |
What changes updated_at
An advocate’s updated_at moves forward whenever anything in their API record changes:
- Their name, contact details, handle, or status changes
- Their profile, address, or map location is edited
- A custom field value changes, or a custom field is added, renamed, or deleted (every advocate carries a key for every field, so all of them change)
- A tag is added, removed, renamed, or reordered. Renaming a tag, moving it to another group, or renaming its group changes its path and marks the advocate as updated, so
filter[updated_since]picks it up. - A wallet balance changes (points are credited, redeemed, or reset), or a wallet is renamed or reordered
- A conversation with them is started
- They are deleted, or their personal data is erased
It can also move when something outside the API record changes, such as their avatar. Updating by id makes the repeat harmless.
Deleted and erased advocates
Deleting an advocate in the admin portal keeps their record so their conversations still make sense, and the API keeps returning it. When an advocate’s personal data is erased (for example, after a privacy request), their record stays too, with the personal data overwritten: first_name becomes [redacted], other contact fields become null or a placeholder, and the address and custom field values are removed.
Erasure moves updated_at, so your next incremental sync receives the redacted record. Apply it to your copy like any other update so the personal data is removed from your systems too.