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

GET /api/v1/advocates

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.

Response advocates-index.json Download
{
"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

GET /api/v1/advocates/01k5exampleadv0cate0000001

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

Response advocates-show.json Download
{
"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:

Response advocates-show-without-pii.json Download
{
"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.


© 2024-2026 Stoked — Real conversations. Real trust.