Update a stewardship-plan move
Uses the move version, not the plan version. Canceled moves cannot be edited. Reopen terminal moves before changing schedule, owner or contact; unlink Task and interaction evidence before changing contact. The status field is prohibited. 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 PUT \
--url https://api.gratona.com/v1/admin/fundraising/stewardship-plans/{plan_id}/moves/{move_id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"version": 1,
"kind": "qualification",
"title": "Field update from the education program",
"description": "Description",
"planned_at": "2026-10-20T12:00:00Z",
"action_donor_id": 1,
"owner_user_id": 1,
"sequence": 1
}
'import requests
url = "https://api.gratona.com/v1/admin/fundraising/stewardship-plans/{plan_id}/moves/{move_id}"
payload = {
"version": 1,
"kind": "qualification",
"title": "Field update from the education program",
"description": "Description",
"planned_at": "2026-10-20T12:00:00Z",
"action_donor_id": 1,
"owner_user_id": 1,
"sequence": 1
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
version: 1,
kind: 'qualification',
title: 'Field update from the education program',
description: 'Description',
planned_at: '2026-10-20T12:00:00Z',
action_donor_id: 1,
owner_user_id: 1,
sequence: 1
})
};
fetch('https://api.gratona.com/v1/admin/fundraising/stewardship-plans/{plan_id}/moves/{move_id}', 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}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'version' => 1,
'kind' => 'qualification',
'title' => 'Field update from the education program',
'description' => 'Description',
'planned_at' => '2026-10-20T12:00:00Z',
'action_donor_id' => 1,
'owner_user_id' => 1,
'sequence' => 1
]),
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}"
payload := strings.NewReader("{\n \"version\": 1,\n \"kind\": \"qualification\",\n \"title\": \"Field update from the education program\",\n \"description\": \"Description\",\n \"planned_at\": \"2026-10-20T12:00:00Z\",\n \"action_donor_id\": 1,\n \"owner_user_id\": 1,\n \"sequence\": 1\n}")
req, _ := http.NewRequest("PUT", 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.put("https://api.gratona.com/v1/admin/fundraising/stewardship-plans/{plan_id}/moves/{move_id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"version\": 1,\n \"kind\": \"qualification\",\n \"title\": \"Field update from the education program\",\n \"description\": \"Description\",\n \"planned_at\": \"2026-10-20T12:00:00Z\",\n \"action_donor_id\": 1,\n \"owner_user_id\": 1,\n \"sequence\": 1\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.gratona.com/v1/admin/fundraising/stewardship-plans/{plan_id}/moves/{move_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"version\": 1,\n \"kind\": \"qualification\",\n \"title\": \"Field update from the education program\",\n \"description\": \"Description\",\n \"planned_at\": \"2026-10-20T12:00:00Z\",\n \"action_donor_id\": 1,\n \"owner_user_id\": 1,\n \"sequence\": 1\n}"
response = http.request(request)
puts response.read_body{
"status": "success",
"data": {
"id": 42,
"version": 2,
"subject": {
"type": "donor",
"id": 42,
"display_name": "Example",
"kind": "Example",
"assigned_fundraiser_user_id": 42,
"relationship_manager": {
"id": 42,
"display_name": "Example"
},
"action_donors": [
{
"id": 42,
"display_name": "Example",
"email": "Example",
"do_not_email": true,
"status": "Example",
"role": "Example",
"relationship_label": "Example",
"is_primary_contact": true
}
],
"available": true,
"archived": true
},
"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",
"completion_outcome": "Example",
"qualification_template_version": 2,
"qualification_progress": {
"completed": 2,
"skipped": 2,
"total": 2
},
"permissions": {
"can_manage": true,
"can_view_strategy": true
},
"strategy_reviewed_at": "2026-10-20T12:00:00Z",
"next_review_at": "2026-10-20T12:00:00Z",
"target_ask_amount": 1000,
"expected_giving_month": 10,
"expected_giving_note": "Example",
"closure_reason": "Example",
"strategy_summary": "Example",
"interests_summary": "Example",
"communication_guidance": "Example",
"moves": [
{
"id": 42,
"version": 2,
"kind": "cultivation",
"template_key": "Example",
"sequence": 2,
"title": "Example",
"description": "Example",
"planned_at": "2026-10-20T12:00:00Z",
"status": "planned",
"action_donor_id": 42,
"owner_user_id": 42,
"task_id": 42,
"completion_source": "manual",
"outcome_summary": "Example",
"completed_at": "2026-10-20T12:00:00Z",
"status_reason": "Example",
"evidence": [
{
"id": 42,
"type": "activity",
"record_id": 42,
"role": "completion",
"available": true,
"is_contact": true,
"is_meaningful": true,
"meaningful_basis": "Example",
"exclusion_reason": "Example"
}
]
}
],
"reviews": [
{
"id": 42,
"review_type": "strategy",
"outcome": "Example",
"summary": "Example",
"redacted_at": "2026-10-20T12:00:00Z",
"reviewed_at": "2026-10-20T12:00:00Z",
"reviewed_by_user_id": 42
}
],
"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
Kind. Validation: sometimes; allowed values: qualification, research, cultivation, solicitation, stewardship, other.
qualification, research, cultivation, solicitation, stewardship, other "qualification"
Title. Validation: sometimes, string, max:255.
255"Field update from the education program"
Description. Validation: nullable, string, max:5000.
5000"Description"
Planned at. Validation: nullable, date.
"2026-10-20T12:00:00Z"
Action donor id. Validation: nullable, integer, min:1.
x >= 11
Owner user id. Validation: nullable, integer, min:1.
x >= 11
Sequence. Validation: nullable, integer, between:1,65535.
1 <= x <= 655351
curl --request PUT \
--url https://api.gratona.com/v1/admin/fundraising/stewardship-plans/{plan_id}/moves/{move_id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"version": 1,
"kind": "qualification",
"title": "Field update from the education program",
"description": "Description",
"planned_at": "2026-10-20T12:00:00Z",
"action_donor_id": 1,
"owner_user_id": 1,
"sequence": 1
}
'import requests
url = "https://api.gratona.com/v1/admin/fundraising/stewardship-plans/{plan_id}/moves/{move_id}"
payload = {
"version": 1,
"kind": "qualification",
"title": "Field update from the education program",
"description": "Description",
"planned_at": "2026-10-20T12:00:00Z",
"action_donor_id": 1,
"owner_user_id": 1,
"sequence": 1
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
version: 1,
kind: 'qualification',
title: 'Field update from the education program',
description: 'Description',
planned_at: '2026-10-20T12:00:00Z',
action_donor_id: 1,
owner_user_id: 1,
sequence: 1
})
};
fetch('https://api.gratona.com/v1/admin/fundraising/stewardship-plans/{plan_id}/moves/{move_id}', 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}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'version' => 1,
'kind' => 'qualification',
'title' => 'Field update from the education program',
'description' => 'Description',
'planned_at' => '2026-10-20T12:00:00Z',
'action_donor_id' => 1,
'owner_user_id' => 1,
'sequence' => 1
]),
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}"
payload := strings.NewReader("{\n \"version\": 1,\n \"kind\": \"qualification\",\n \"title\": \"Field update from the education program\",\n \"description\": \"Description\",\n \"planned_at\": \"2026-10-20T12:00:00Z\",\n \"action_donor_id\": 1,\n \"owner_user_id\": 1,\n \"sequence\": 1\n}")
req, _ := http.NewRequest("PUT", 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.put("https://api.gratona.com/v1/admin/fundraising/stewardship-plans/{plan_id}/moves/{move_id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"version\": 1,\n \"kind\": \"qualification\",\n \"title\": \"Field update from the education program\",\n \"description\": \"Description\",\n \"planned_at\": \"2026-10-20T12:00:00Z\",\n \"action_donor_id\": 1,\n \"owner_user_id\": 1,\n \"sequence\": 1\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.gratona.com/v1/admin/fundraising/stewardship-plans/{plan_id}/moves/{move_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"version\": 1,\n \"kind\": \"qualification\",\n \"title\": \"Field update from the education program\",\n \"description\": \"Description\",\n \"planned_at\": \"2026-10-20T12:00:00Z\",\n \"action_donor_id\": 1,\n \"owner_user_id\": 1,\n \"sequence\": 1\n}"
response = http.request(request)
puts response.read_body{
"status": "success",
"data": {
"id": 42,
"version": 2,
"subject": {
"type": "donor",
"id": 42,
"display_name": "Example",
"kind": "Example",
"assigned_fundraiser_user_id": 42,
"relationship_manager": {
"id": 42,
"display_name": "Example"
},
"action_donors": [
{
"id": 42,
"display_name": "Example",
"email": "Example",
"do_not_email": true,
"status": "Example",
"role": "Example",
"relationship_label": "Example",
"is_primary_contact": true
}
],
"available": true,
"archived": true
},
"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",
"completion_outcome": "Example",
"qualification_template_version": 2,
"qualification_progress": {
"completed": 2,
"skipped": 2,
"total": 2
},
"permissions": {
"can_manage": true,
"can_view_strategy": true
},
"strategy_reviewed_at": "2026-10-20T12:00:00Z",
"next_review_at": "2026-10-20T12:00:00Z",
"target_ask_amount": 1000,
"expected_giving_month": 10,
"expected_giving_note": "Example",
"closure_reason": "Example",
"strategy_summary": "Example",
"interests_summary": "Example",
"communication_guidance": "Example",
"moves": [
{
"id": 42,
"version": 2,
"kind": "cultivation",
"template_key": "Example",
"sequence": 2,
"title": "Example",
"description": "Example",
"planned_at": "2026-10-20T12:00:00Z",
"status": "planned",
"action_donor_id": 42,
"owner_user_id": 42,
"task_id": 42,
"completion_source": "manual",
"outcome_summary": "Example",
"completed_at": "2026-10-20T12:00:00Z",
"status_reason": "Example",
"evidence": [
{
"id": 42,
"type": "activity",
"record_id": 42,
"role": "completion",
"available": true,
"is_contact": true,
"is_meaningful": true,
"meaningful_basis": "Example",
"exclusion_reason": "Example"
}
]
}
],
"reviews": [
{
"id": 42,
"review_type": "strategy",
"outcome": "Example",
"summary": "Example",
"redacted_at": "2026-10-20T12:00:00Z",
"reviewed_at": "2026-10-20T12:00:00Z",
"reviewed_by_user_id": 42
}
],
"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."
}