Calculate donor worksheet footer totals
Aggregates every matching donor, independently of pagination, using SQL aggregates or bounded row projection. Validates the calculation array, then keeps allowed field keys/operations, case-normalizes operations and deduplicates by field. Invalid entries can leave an empty calculations array with length0. Filled checks ignore blank, HTML-only and No data values; date results use Unix seconds and display formatting. Requires permission: one of donors_manage.
curl --request POST \
--url https://api.gratona.com/v1/admin/donors/{hysform_id}/footer-calculations \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"footer_calculations": [
{
"field_key": "lifetime_contributions",
"operation": "sum"
}
],
"filter": "false",
"filterGroups": [
{
"field": "display_name",
"operator": "contains",
"value": "<unknown>",
"logicalOperator": "AND"
}
],
"query": "Jane",
"keysArray": [
"display_name"
],
"comparisonOperator": "contains",
"savedFilterId": 42,
"saved_filter_id": 42,
"fields": [
"display_name"
],
"column_order": [
"display_name"
],
"date_from": "2026-01-01",
"date_to": "2026-10-20"
}
'import requests
url = "https://api.gratona.com/v1/admin/donors/{hysform_id}/footer-calculations"
payload = {
"footer_calculations": [
{
"field_key": "lifetime_contributions",
"operation": "sum"
}
],
"filter": "false",
"filterGroups": [
{
"field": "display_name",
"operator": "contains",
"value": "<unknown>",
"logicalOperator": "AND"
}
],
"query": "Jane",
"keysArray": ["display_name"],
"comparisonOperator": "contains",
"savedFilterId": 42,
"saved_filter_id": 42,
"fields": ["display_name"],
"column_order": ["display_name"],
"date_from": "2026-01-01",
"date_to": "2026-10-20"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
footer_calculations: [{field_key: 'lifetime_contributions', operation: 'sum'}],
filter: 'false',
filterGroups: [
{
field: 'display_name',
operator: 'contains',
value: '<unknown>',
logicalOperator: 'AND'
}
],
query: 'Jane',
keysArray: ['display_name'],
comparisonOperator: 'contains',
savedFilterId: 42,
saved_filter_id: 42,
fields: ['display_name'],
column_order: ['display_name'],
date_from: '2026-01-01',
date_to: '2026-10-20'
})
};
fetch('https://api.gratona.com/v1/admin/donors/{hysform_id}/footer-calculations', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.gratona.com/v1/admin/donors/{hysform_id}/footer-calculations",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'footer_calculations' => [
[
'field_key' => 'lifetime_contributions',
'operation' => 'sum'
]
],
'filter' => 'false',
'filterGroups' => [
[
'field' => 'display_name',
'operator' => 'contains',
'value' => '<unknown>',
'logicalOperator' => 'AND'
]
],
'query' => 'Jane',
'keysArray' => [
'display_name'
],
'comparisonOperator' => 'contains',
'savedFilterId' => 42,
'saved_filter_id' => 42,
'fields' => [
'display_name'
],
'column_order' => [
'display_name'
],
'date_from' => '2026-01-01',
'date_to' => '2026-10-20'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.gratona.com/v1/admin/donors/{hysform_id}/footer-calculations"
payload := strings.NewReader("{\n \"footer_calculations\": [\n {\n \"field_key\": \"lifetime_contributions\",\n \"operation\": \"sum\"\n }\n ],\n \"filter\": \"false\",\n \"filterGroups\": [\n {\n \"field\": \"display_name\",\n \"operator\": \"contains\",\n \"value\": \"<unknown>\",\n \"logicalOperator\": \"AND\"\n }\n ],\n \"query\": \"Jane\",\n \"keysArray\": [\n \"display_name\"\n ],\n \"comparisonOperator\": \"contains\",\n \"savedFilterId\": 42,\n \"saved_filter_id\": 42,\n \"fields\": [\n \"display_name\"\n ],\n \"column_order\": [\n \"display_name\"\n ],\n \"date_from\": \"2026-01-01\",\n \"date_to\": \"2026-10-20\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.gratona.com/v1/admin/donors/{hysform_id}/footer-calculations")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"footer_calculations\": [\n {\n \"field_key\": \"lifetime_contributions\",\n \"operation\": \"sum\"\n }\n ],\n \"filter\": \"false\",\n \"filterGroups\": [\n {\n \"field\": \"display_name\",\n \"operator\": \"contains\",\n \"value\": \"<unknown>\",\n \"logicalOperator\": \"AND\"\n }\n ],\n \"query\": \"Jane\",\n \"keysArray\": [\n \"display_name\"\n ],\n \"comparisonOperator\": \"contains\",\n \"savedFilterId\": 42,\n \"saved_filter_id\": 42,\n \"fields\": [\n \"display_name\"\n ],\n \"column_order\": [\n \"display_name\"\n ],\n \"date_from\": \"2026-01-01\",\n \"date_to\": \"2026-10-20\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.gratona.com/v1/admin/donors/{hysform_id}/footer-calculations")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"footer_calculations\": [\n {\n \"field_key\": \"lifetime_contributions\",\n \"operation\": \"sum\"\n }\n ],\n \"filter\": \"false\",\n \"filterGroups\": [\n {\n \"field\": \"display_name\",\n \"operator\": \"contains\",\n \"value\": \"<unknown>\",\n \"logicalOperator\": \"AND\"\n }\n ],\n \"query\": \"Jane\",\n \"keysArray\": [\n \"display_name\"\n ],\n \"comparisonOperator\": \"contains\",\n \"savedFilterId\": 42,\n \"saved_filter_id\": 42,\n \"fields\": [\n \"display_name\"\n ],\n \"column_order\": [\n \"display_name\"\n ],\n \"date_from\": \"2026-01-01\",\n \"date_to\": \"2026-10-20\"\n}"
response = http.request(request)
puts response.read_body{
"calculations": {},
"length": 42
}{
"message": "Unauthorized",
"status": "error"
}{
"errors": {},
"status": "error",
"message": "The given data was invalid."
}Authorizations
Passport bearer token authentication for legacy admin sessions.
Path Parameters
Donor form id or all forms.
42
Body
Validated request fields for this operation.
Calculates over the complete matching donor scope, ignoring row pagination. Duplicate field keys resolve to the final valid calculation; invalid/unauthorized fields or operations are discarded.
1Show child attributes
Show child attributes
Archived-view selector. Use string false for active and true for archived. Recipient views also support available, sponsored and unsponsored.
"false"
Multiple typed comparisons. Requires filter when present; value is required except supported presence/empty operators. Saved-view filters are sanitized to current allowed fields.
Show child attributes
Show child attributes
Worksheet search text; recipient search defaults to title/id fields when keysArray is absent.
"Jane"
Legacy comparison/search field keys.
Legacy single comparison operator.
"contains"
Saved view/report from this organization and worksheet module; missing/unmatched ids are ignored.
42
Compatibility alias for savedFilterId.
42
Selected column keys; defaults to saved/current form preferences.
Preferred allowed column ordering.
Lower date bound, interpreted with module/saved report date semantics.
"2026-01-01"
Upper date bound; donor created-at range applies when both bounds are supplied and no advanced date override exists.
"2026-10-20"
curl --request POST \
--url https://api.gratona.com/v1/admin/donors/{hysform_id}/footer-calculations \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"footer_calculations": [
{
"field_key": "lifetime_contributions",
"operation": "sum"
}
],
"filter": "false",
"filterGroups": [
{
"field": "display_name",
"operator": "contains",
"value": "<unknown>",
"logicalOperator": "AND"
}
],
"query": "Jane",
"keysArray": [
"display_name"
],
"comparisonOperator": "contains",
"savedFilterId": 42,
"saved_filter_id": 42,
"fields": [
"display_name"
],
"column_order": [
"display_name"
],
"date_from": "2026-01-01",
"date_to": "2026-10-20"
}
'import requests
url = "https://api.gratona.com/v1/admin/donors/{hysform_id}/footer-calculations"
payload = {
"footer_calculations": [
{
"field_key": "lifetime_contributions",
"operation": "sum"
}
],
"filter": "false",
"filterGroups": [
{
"field": "display_name",
"operator": "contains",
"value": "<unknown>",
"logicalOperator": "AND"
}
],
"query": "Jane",
"keysArray": ["display_name"],
"comparisonOperator": "contains",
"savedFilterId": 42,
"saved_filter_id": 42,
"fields": ["display_name"],
"column_order": ["display_name"],
"date_from": "2026-01-01",
"date_to": "2026-10-20"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
footer_calculations: [{field_key: 'lifetime_contributions', operation: 'sum'}],
filter: 'false',
filterGroups: [
{
field: 'display_name',
operator: 'contains',
value: '<unknown>',
logicalOperator: 'AND'
}
],
query: 'Jane',
keysArray: ['display_name'],
comparisonOperator: 'contains',
savedFilterId: 42,
saved_filter_id: 42,
fields: ['display_name'],
column_order: ['display_name'],
date_from: '2026-01-01',
date_to: '2026-10-20'
})
};
fetch('https://api.gratona.com/v1/admin/donors/{hysform_id}/footer-calculations', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.gratona.com/v1/admin/donors/{hysform_id}/footer-calculations",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'footer_calculations' => [
[
'field_key' => 'lifetime_contributions',
'operation' => 'sum'
]
],
'filter' => 'false',
'filterGroups' => [
[
'field' => 'display_name',
'operator' => 'contains',
'value' => '<unknown>',
'logicalOperator' => 'AND'
]
],
'query' => 'Jane',
'keysArray' => [
'display_name'
],
'comparisonOperator' => 'contains',
'savedFilterId' => 42,
'saved_filter_id' => 42,
'fields' => [
'display_name'
],
'column_order' => [
'display_name'
],
'date_from' => '2026-01-01',
'date_to' => '2026-10-20'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.gratona.com/v1/admin/donors/{hysform_id}/footer-calculations"
payload := strings.NewReader("{\n \"footer_calculations\": [\n {\n \"field_key\": \"lifetime_contributions\",\n \"operation\": \"sum\"\n }\n ],\n \"filter\": \"false\",\n \"filterGroups\": [\n {\n \"field\": \"display_name\",\n \"operator\": \"contains\",\n \"value\": \"<unknown>\",\n \"logicalOperator\": \"AND\"\n }\n ],\n \"query\": \"Jane\",\n \"keysArray\": [\n \"display_name\"\n ],\n \"comparisonOperator\": \"contains\",\n \"savedFilterId\": 42,\n \"saved_filter_id\": 42,\n \"fields\": [\n \"display_name\"\n ],\n \"column_order\": [\n \"display_name\"\n ],\n \"date_from\": \"2026-01-01\",\n \"date_to\": \"2026-10-20\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.gratona.com/v1/admin/donors/{hysform_id}/footer-calculations")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"footer_calculations\": [\n {\n \"field_key\": \"lifetime_contributions\",\n \"operation\": \"sum\"\n }\n ],\n \"filter\": \"false\",\n \"filterGroups\": [\n {\n \"field\": \"display_name\",\n \"operator\": \"contains\",\n \"value\": \"<unknown>\",\n \"logicalOperator\": \"AND\"\n }\n ],\n \"query\": \"Jane\",\n \"keysArray\": [\n \"display_name\"\n ],\n \"comparisonOperator\": \"contains\",\n \"savedFilterId\": 42,\n \"saved_filter_id\": 42,\n \"fields\": [\n \"display_name\"\n ],\n \"column_order\": [\n \"display_name\"\n ],\n \"date_from\": \"2026-01-01\",\n \"date_to\": \"2026-10-20\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.gratona.com/v1/admin/donors/{hysform_id}/footer-calculations")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"footer_calculations\": [\n {\n \"field_key\": \"lifetime_contributions\",\n \"operation\": \"sum\"\n }\n ],\n \"filter\": \"false\",\n \"filterGroups\": [\n {\n \"field\": \"display_name\",\n \"operator\": \"contains\",\n \"value\": \"<unknown>\",\n \"logicalOperator\": \"AND\"\n }\n ],\n \"query\": \"Jane\",\n \"keysArray\": [\n \"display_name\"\n ],\n \"comparisonOperator\": \"contains\",\n \"savedFilterId\": 42,\n \"saved_filter_id\": 42,\n \"fields\": [\n \"display_name\"\n ],\n \"column_order\": [\n \"display_name\"\n ],\n \"date_from\": \"2026-01-01\",\n \"date_to\": \"2026-10-20\"\n}"
response = http.request(request)
puts response.read_body{
"calculations": {},
"length": 42
}{
"message": "Unauthorized",
"status": "error"
}{
"errors": {},
"status": "error",
"message": "The given data was invalid."
}