Link interaction evidence to a stewardship move
Uses the move version. Evidence must be accessible and linked to its action contact. Completion evidence completes the move; future or canceled calendar events cannot provide completion. Linking an existing record updates its role. Requires stewardship_major_donor_plans_v1. Relationship-group subjects also require stewardship_relationship_groups_v1. Subject access respects the viewer’s donor visibility. Management requires donors_manage/group_all, or donors_view with assignment as this subject’s fundraiser; strategy fields follow the same authority. Requires permission: donors_view; plus one of donors_view, donors_manage.
curl --request POST \
--url https://api.gratona.com/v1/admin/fundraising/stewardship-plans/{plan_id}/moves/{move_id}/evidence \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"version": 1,
"evidence_type": "activity",
"evidence_id": 1,
"evidence_role": "scheduled"
}
'import requests
url = "https://api.gratona.com/v1/admin/fundraising/stewardship-plans/{plan_id}/moves/{move_id}/evidence"
payload = {
"version": 1,
"evidence_type": "activity",
"evidence_id": 1,
"evidence_role": "scheduled"
}
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({
version: 1,
evidence_type: 'activity',
evidence_id: 1,
evidence_role: 'scheduled'
})
};
fetch('https://api.gratona.com/v1/admin/fundraising/stewardship-plans/{plan_id}/moves/{move_id}/evidence', 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/fundraising/stewardship-plans/{plan_id}/moves/{move_id}/evidence",
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([
'version' => 1,
'evidence_type' => 'activity',
'evidence_id' => 1,
'evidence_role' => 'scheduled'
]),
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/fundraising/stewardship-plans/{plan_id}/moves/{move_id}/evidence"
payload := strings.NewReader("{\n \"version\": 1,\n \"evidence_type\": \"activity\",\n \"evidence_id\": 1,\n \"evidence_role\": \"scheduled\"\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/fundraising/stewardship-plans/{plan_id}/moves/{move_id}/evidence")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"version\": 1,\n \"evidence_type\": \"activity\",\n \"evidence_id\": 1,\n \"evidence_role\": \"scheduled\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.gratona.com/v1/admin/fundraising/stewardship-plans/{plan_id}/moves/{move_id}/evidence")
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 \"version\": 1,\n \"evidence_type\": \"activity\",\n \"evidence_id\": 1,\n \"evidence_role\": \"scheduled\"\n}"
response = http.request(request)
puts response.read_body{
"status": "success",
"data": {
"move_id": 42,
"version": 2,
"warnings": [
{
"code": "work_projection_delayed",
"message": "Example"
}
]
}
}{
"message": "Unauthorized",
"status": "error"
}{
"message": "Not Found",
"status": "error"
}{
"status": "error",
"code": "PLAN_VERSION_CONFLICT",
"message": "Example",
"data": {
"latest": {
"id": 42,
"version": 2,
"subject": {
"type": "donor",
"id": 42
},
"stage": "qualifying",
"status": "draft",
"tier": "A",
"stage_entered_at": "2026-10-20T12:00:00Z",
"action_donor_id": 42,
"period": {
"start": "2026-10-20",
"end": "2026-10-20"
},
"currency": "USD",
"goal_amount": 1000,
"attributed_actual": 1000,
"direct_actual": 1000,
"soft_credit_actual": 1000,
"variance_amount": 1000,
"gift_count": 2,
"giving_status": "pending",
"giving_last_successful_at": "2026-10-20T12:00:00Z",
"currency_breakdown": [
{
"currency": "Example",
"attributed_actual": 1000,
"direct_actual": 1000,
"soft_credit_actual": 1000,
"gift_count": 2
}
],
"next_move": {
"id": 42,
"title": "Example",
"planned_at": "2026-10-20T12:00:00Z"
},
"open_move_count": 2,
"overdue_move_count": 2,
"health": {
"status": "Example",
"reasons": [
"overdue_move"
]
},
"metric_basis": "relationship_attribution"
},
"conflicting_plan": {}
}
}{
"errors": {},
"status": "error",
"message": "The given data was invalid."
}Authorizations
Passport bearer token authentication for legacy admin sessions.
Path Parameters
Plan id in the current organization.
Move id in the current organization.
Body
Validated request fields for this operation.
Current move version for optimistic concurrency. Stale versions return 409.
x >= 11
Evidence type. Validation: required; allowed values: activity, calendar_event.
activity, calendar_event "activity"
Evidence id. Validation: required, integer, min:1.
x >= 11
Evidence role. Validation: required; allowed values: scheduled, completion.
scheduled, completion "scheduled"
curl --request POST \
--url https://api.gratona.com/v1/admin/fundraising/stewardship-plans/{plan_id}/moves/{move_id}/evidence \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"version": 1,
"evidence_type": "activity",
"evidence_id": 1,
"evidence_role": "scheduled"
}
'import requests
url = "https://api.gratona.com/v1/admin/fundraising/stewardship-plans/{plan_id}/moves/{move_id}/evidence"
payload = {
"version": 1,
"evidence_type": "activity",
"evidence_id": 1,
"evidence_role": "scheduled"
}
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({
version: 1,
evidence_type: 'activity',
evidence_id: 1,
evidence_role: 'scheduled'
})
};
fetch('https://api.gratona.com/v1/admin/fundraising/stewardship-plans/{plan_id}/moves/{move_id}/evidence', 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/fundraising/stewardship-plans/{plan_id}/moves/{move_id}/evidence",
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([
'version' => 1,
'evidence_type' => 'activity',
'evidence_id' => 1,
'evidence_role' => 'scheduled'
]),
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/fundraising/stewardship-plans/{plan_id}/moves/{move_id}/evidence"
payload := strings.NewReader("{\n \"version\": 1,\n \"evidence_type\": \"activity\",\n \"evidence_id\": 1,\n \"evidence_role\": \"scheduled\"\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/fundraising/stewardship-plans/{plan_id}/moves/{move_id}/evidence")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"version\": 1,\n \"evidence_type\": \"activity\",\n \"evidence_id\": 1,\n \"evidence_role\": \"scheduled\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.gratona.com/v1/admin/fundraising/stewardship-plans/{plan_id}/moves/{move_id}/evidence")
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 \"version\": 1,\n \"evidence_type\": \"activity\",\n \"evidence_id\": 1,\n \"evidence_role\": \"scheduled\"\n}"
response = http.request(request)
puts response.read_body{
"status": "success",
"data": {
"move_id": 42,
"version": 2,
"warnings": [
{
"code": "work_projection_delayed",
"message": "Example"
}
]
}
}{
"message": "Unauthorized",
"status": "error"
}{
"message": "Not Found",
"status": "error"
}{
"status": "error",
"code": "PLAN_VERSION_CONFLICT",
"message": "Example",
"data": {
"latest": {
"id": 42,
"version": 2,
"subject": {
"type": "donor",
"id": 42
},
"stage": "qualifying",
"status": "draft",
"tier": "A",
"stage_entered_at": "2026-10-20T12:00:00Z",
"action_donor_id": 42,
"period": {
"start": "2026-10-20",
"end": "2026-10-20"
},
"currency": "USD",
"goal_amount": 1000,
"attributed_actual": 1000,
"direct_actual": 1000,
"soft_credit_actual": 1000,
"variance_amount": 1000,
"gift_count": 2,
"giving_status": "pending",
"giving_last_successful_at": "2026-10-20T12:00:00Z",
"currency_breakdown": [
{
"currency": "Example",
"attributed_actual": 1000,
"direct_actual": 1000,
"soft_credit_actual": 1000,
"gift_count": 2
}
],
"next_move": {
"id": 42,
"title": "Example",
"planned_at": "2026-10-20T12:00:00Z"
},
"open_move_count": 2,
"overdue_move_count": 2,
"health": {
"status": "Example",
"reasons": [
"overdue_move"
]
},
"metric_basis": "relationship_attribution"
},
"conflicting_plan": {}
}
}{
"errors": {},
"status": "error",
"message": "The given data was invalid."
}