Applications API

Read every application submitted through your community site’s join form: who applied, how they answered your application questions, the photos they uploaded, and what you decided. Once an application is approved, it links to the advocate record it became.

Requires the applications permission. An application is mostly personal data, so there is no separate :pii permission: the one permission includes the applicant’s name, email, phone, address, answers and photos.

Endpoint Returns
GET /api/v1/applications A paginated list of applications
GET /api/v1/applications/:id One application

List applications

GET https://integrations.stokedhq.com/api/v1/applications

With no filters, the list contains every application your community has received through Stoked, whatever its status. Applications collected before your community started using Stoked aren’t in Stoked, so the API can’t return them.

Parameters

Parameter Description
filter[status] pending, approved or rejected, or several separated by commas
filter[created_since] Only applications submitted at or after this time
filter[updated_since] Only applications updated at or after this time. Use it for incremental syncing.
page[size] Records 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.

An unknown status, or any other parameter, returns a 400 error. Applications come back least recently updated first.

Request

GET /api/v1/applications

cURL

curl -G "https://integrations.stokedhq.com/api/v1/applications" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/vnd.api+json" \
--data-urlencode "filter[updated_since]=2026-08-01T00:00:00Z"

Ruby

require "net/http"
require "json"
uri = URI("https://integrations.stokedhq.com/api/v1/applications")
uri.query = URI.encode_www_form("filter[updated_since]" => "2026-08-01T00:00:00Z")
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/applications",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Accept": "application/vnd.api+json",
},
params={"filter[updated_since]": "2026-08-01T00:00:00Z"},
)
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/applications?filter[updated_since]=2026-08-01T00:00:00Z");
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/applications");
url.searchParams.set("filter[updated_since]", "2026-08-01T00:00:00Z");
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 applications-index.json Download
{
"data": [
{
"type": "applications",
"id": "01k5exampleappl1cat10n0001",
"attributes": {
"status": "approved",
"status_changed_at": "2026-08-20T14:00:00Z",
"first_name": "Sarah",
"last_name": "Example",
"email": "sarah@example.com",
"phone_number": "+12025550100",
"address": {
"address_1": "123 Example St",
"address_2": null,
"city": "Springfield",
"state": "IL",
"postal_code": "62701",
"country_code": "USA"
},
"latitude": null,
"longitude": null,
"testimonial": "Our longtail replaced the second car.",
"answers": {
"bike_model": "Longtail",
"interests": [
"Test rides",
"Social posts"
]
},
"avatar": {
"url": "https://storage.stokedhq.com/blobs/proxy/EXAMPLE-SIGNED-ID/sarah.png",
"filename": "sarah.png",
"content_type": "image/png",
"byte_size": 59099,
"checksum": "Vg8Sq0fxeT1qchDorvJuZQ=="
},
"photos": [],
"rejection_reason_id": null,
"rejection_note": null,
"created_at": "2026-08-18T09:00:00Z",
"updated_at": "2026-08-20T14:00:00Z"
},
"relationships": {
"advocate": {
"data": {
"type": "advocates",
"id": "01k5exampleadv0cate0000001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/advocates/01k5exampleadv0cate0000001"
}
}
},
"links": {
"self": "https://integrations.stokedhq.com/api/v1/applications/01k5exampleappl1cat10n0001"
}
}
],
"links": {
"self": "https://integrations.stokedhq.com/api/v1/applications?filter%5Bupdated_since%5D=2026-08-01T00%3A00%3A00Z&page%5Bnumber%5D=1&page%5Bsize%5D=50",
"first": "https://integrations.stokedhq.com/api/v1/applications?filter%5Bupdated_since%5D=2026-08-01T00%3A00%3A00Z&page%5Bnumber%5D=1&page%5Bsize%5D=50",
"prev": null,
"next": null,
"last": "https://integrations.stokedhq.com/api/v1/applications?filter%5Bupdated_since%5D=2026-08-01T00%3A00%3A00Z&page%5Bnumber%5D=1&page%5Bsize%5D=50"
},
"meta": {
"page": 1,
"per_page": 50,
"total_count": 1,
"total_pages": 1,
"application_questions": [
{
"id": "01k5exampleappquest10n0001",
"identifier": "bike_model",
"label": "Which bike do you ride?",
"field_type": "short_text",
"response_options": [],
"required": false,
"position": 1
},
{
"id": "01k5exampleappquest10n0002",
"identifier": "interests",
"label": "How would you like to help?",
"field_type": "multiple_select",
"response_options": [
"Test rides",
"Community events",
"Social posts"
],
"required": false,
"position": 2
}
],
"rejection_reasons": [
{
"id": "01k5examplereject10n000001",
"name": "Outside our area",
"description": "There is no Squad near the applicant yet.",
"system": false
}
]
}
}

Get an application

GET https://integrations.stokedhq.com/api/v1/applications/:id

Returns the same record as the list, with the same meta. An ID that doesn’t exist in your community returns 404.

GET /api/v1/applications/01k5exampleappl1cat10n0001

cURL

curl "https://integrations.stokedhq.com/api/v1/applications/01k5exampleappl1cat10n0001" \
-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/applications/01k5exampleappl1cat10n0001")
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/applications/01k5exampleappl1cat10n0001",
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/applications/01k5exampleappl1cat10n0001");
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/applications/01k5exampleappl1cat10n0001");
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);

Attributes

Records with "type": "applications".

Attribute Type Description
status string pending, approved or rejected
status_changed_at timestamp or null When the application was approved or rejected. null while it is pending.
first_name, last_name string The applicant’s name as they entered it
email string The applicant’s email address
phone_number string or null The applicant’s phone number in E.164 format, such as +12025550100. null once erased.
address object or null {address_1, address_2, city, state, postal_code, country_code}, the same shape as an advocate’s address. country_code is ISO 3166-1 alpha-3, such as USA.
latitude, longitude number or null Map coordinates for the address. Stoked doesn’t look up coordinates for application addresses, so these are usually null.
testimonial string or null What the applicant wrote about your product
answers object The applicant’s answers to your application questions. See Answers.
avatar object or null The profile photo the applicant uploaded. See Photos.
photos array Any other photos the applicant uploaded. Empty when there are none.
rejection_reason_id string or null Why the application was rejected: its entry in meta.rejection_reasons. null unless rejected.
rejection_note string or null The note the admin wrote when rejecting it
created_at timestamp When the applicant submitted the application
updated_at timestamp When anything above last changed

Answers

answers is an object keyed by each question’s identifier, as it was when the applicant answered. Every response carries your community’s current questions in its top-level meta.application_questions: an array of {id, identifier, label, field_type, response_options, required, position}, in the order the form shows them.

Values are formatted like an advocate’s custom_fields: a multiple_select answer is an array of the chosen options, and everything else is a string (a ticked checkbox is "Yes"; an unticked one counts as blank, see below).

  • A question the applicant left blank is not in answers. Stoked doesn’t store blank answers, so it can’t tell a skipped question from one that wasn’t on the form yet.
  • If you rename a question’s identifier or delete a question, older applications keep the identifier they were answered under. That key may then be missing from meta.application_questions, or, if a newer question reuses the identifier, match that newer question rather than the one the applicant answered.
  • Once a question is deleted, its answers are served exactly as stored, since there is no question type left to format them by: a multiple_select answer becomes a JSON-encoded string such as "[\"Test rides\",\"Events\"]", and a checkbox "true" (an unticked checkbox counts as blank, so it is never stored).
  • Once an applicant is erased at their request, every answer is the string "[redacted]", including multiple_select answers. See What updates updated_at.
  • Tag questions aren’t in answers, though they are listed in meta.application_questions with field_type "tags". Their picks become tag suggestions you accept or dismiss when reviewing the application.

Photos

avatar and each entry in photos are objects:

Field Type Description
url string Where to download the original file. The URL doesn’t expire, and Stoked serves the file on each request, so download it rather than embedding the URL somewhere public. Anyone who has the URL can download the file without an API key.
filename string The file’s name as uploaded
content_type string Such as image/jpeg or image/png
byte_size integer The file’s size in bytes
checksum string The Base64-encoded MD5 of the file. Compare it with your stored copy to skip downloading a photo you already have.

Rejection reasons

Every response also carries your community’s rejection reasons in meta.rejection_reasons: an array of {id, name, description, system}, in the order set under Settings > Close Reasons > Applications (Close Reasons). Look up a rejected application’s rejection_reason_id there. system is true only for “Unknown”, the built-in reason that applications move to when their reason is deleted.


Relationships

Relationship Type Description
advocate advocates The advocate record the application became once approved. null until an approved application is linked to an advocate. An application rejected after it was approved keeps the link.

Following the link needs the advocates permission: the link is a pointer, not a grant.


What updates updated_at

An application’s updated_at changes when it is approved, rejected or reset to pending, when it is linked to an advocate, when its address changes, when its rejection reason is deleted (it moves to “Unknown”), when a question it answered changes type or is deleted (the answer is formatted by the question’s type), and when it is erased at the applicant’s request. Erasure blanks the personal attributes, removes the photos and replaces each answer with [redacted]. The record itself stays, so your copy receives the erasure on the next sync.

Answers and photos never change after an application is submitted. Renaming a question or a rejection reason doesn’t change the application, since meta carries the current definitions on every response.


Keeping a copy in sync

  1. On the first run, follow links.next through everything and upsert each application by id
  2. Remember the newest updated_at you received
  3. On the next run, pass it as filter[updated_since], follow links.next to the end, and upsert each application by id
  4. Download any photo whose checksum you don’t already have

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