Add one donor to a household
Requires an active donor outside any household; already-present or other-household membership returns 422. Stores the requested relationship. Literal boolean make_primary=true selects this donor as primary; optional primary_relationship updates the existing household primary (or fallback primary) relationship. Material stewardship impact returns 409 with an expiring confirmation token and required contact/archive repairs; resubmit the same reviewed change with plan_impact_version and plan_repairs.
curl --request POST \
--url https://api.gratona.com/v1/admin/households/{household_id}/donors/{donor_id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"relationship": "Church",
"primary_relationship": "Church",
"primary": 42,
"make_primary": false,
"plan_impact_version": "issued-household-impact-version",
"plan_repairs": [
{
"plan_id": 42,
"action": "replace_action_donor",
"action_donor_id": 42,
"reason": "Household is being removed."
}
]
}
'import requests
url = "https://api.gratona.com/v1/admin/households/{household_id}/donors/{donor_id}"
payload = {
"relationship": "Church",
"primary_relationship": "Church",
"primary": 42,
"make_primary": False,
"plan_impact_version": "issued-household-impact-version",
"plan_repairs": [
{
"plan_id": 42,
"action": "replace_action_donor",
"action_donor_id": 42,
"reason": "Household is being removed."
}
]
}
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({
relationship: 'Church',
primary_relationship: 'Church',
primary: 42,
make_primary: false,
plan_impact_version: 'issued-household-impact-version',
plan_repairs: [
{
plan_id: 42,
action: 'replace_action_donor',
action_donor_id: 42,
reason: 'Household is being removed.'
}
]
})
};
fetch('https://api.gratona.com/v1/admin/households/{household_id}/donors/{donor_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/households/{household_id}/donors/{donor_id}",
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([
'relationship' => 'Church',
'primary_relationship' => 'Church',
'primary' => 42,
'make_primary' => false,
'plan_impact_version' => 'issued-household-impact-version',
'plan_repairs' => [
[
'plan_id' => 42,
'action' => 'replace_action_donor',
'action_donor_id' => 42,
'reason' => 'Household is being removed.'
]
]
]),
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/households/{household_id}/donors/{donor_id}"
payload := strings.NewReader("{\n \"relationship\": \"Church\",\n \"primary_relationship\": \"Church\",\n \"primary\": 42,\n \"make_primary\": false,\n \"plan_impact_version\": \"issued-household-impact-version\",\n \"plan_repairs\": [\n {\n \"plan_id\": 42,\n \"action\": \"replace_action_donor\",\n \"action_donor_id\": 42,\n \"reason\": \"Household is being removed.\"\n }\n ]\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/households/{household_id}/donors/{donor_id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"relationship\": \"Church\",\n \"primary_relationship\": \"Church\",\n \"primary\": 42,\n \"make_primary\": false,\n \"plan_impact_version\": \"issued-household-impact-version\",\n \"plan_repairs\": [\n {\n \"plan_id\": 42,\n \"action\": \"replace_action_donor\",\n \"action_donor_id\": 42,\n \"reason\": \"Household is being removed.\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.gratona.com/v1/admin/households/{household_id}/donors/{donor_id}")
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 \"relationship\": \"Church\",\n \"primary_relationship\": \"Church\",\n \"primary\": 42,\n \"make_primary\": false,\n \"plan_impact_version\": \"issued-household-impact-version\",\n \"plan_repairs\": [\n {\n \"plan_id\": 42,\n \"action\": \"replace_action_donor\",\n \"action_donor_id\": 42,\n \"reason\": \"Household is being removed.\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"message": "Donor successfully added to household",
"is_primary": false,
"donor": {
"id": 42,
"name": "Jane Doe",
"relationship": "Example"
},
"household": {
"id": 42,
"name": "Doe household",
"donor_ids": [
42
]
}
}{
"message": "Something went wrong. Please try again.",
"status": "error",
"error": "invalid_request",
"code": "DONOR_NOT_FOUND"
}{
"message": "Something went wrong. Please try again.",
"status": "error",
"error": "invalid_request",
"code": "DONOR_NOT_FOUND"
}{
"message": "Not Found",
"status": "error"
}{
"status": "error",
"code": "PLAN_IMPACT_CONFIRMATION_REQUIRED",
"message": "Review household plan impact before saving.",
"data": {
"material_impact": false,
"affected_plans": [
{
"id": 42,
"subject": {
"type": "donor",
"id": 42
},
"stage": "qualifying",
"status": "draft",
"period": {
"start": "2026-10-20",
"end": "2026-10-20"
},
"version": 2,
"action_donor_id": 42,
"scope_version": 2
}
],
"group_plans": [
"<string>"
],
"transfer_candidates": [
"<string>"
],
"scope_changes": [
{
"plan_id": 42,
"next_members": [
{
"member_type": "donor",
"member_id": 42,
"scope_role": "core"
}
],
"eligible_action_donor_ids": [
42
],
"invalid_open_move_ids": [
42
]
}
],
"eligible_action_donor_ids": [
42
],
"collisions": [
{
"plan_id": 42,
"conflicting_plan": {}
}
],
"authority_changes": [
"<string>"
],
"required_repairs": [
{
"plan_id": 42,
"choices": [
"replace_action_donor"
],
"eligible_action_donor_ids": [
42
],
"recommended_action_donor_id": 42
}
],
"proposed_member_donor_ids": [
42
],
"primary_contact_id": 42,
"plan_repairs": [
{
"plan_id": 42
}
],
"command": "household_membership",
"expires_at": "2026-10-20T12:00:00Z",
"impact_version": "issued-household-impact-version",
"conflicts": [
{
"plan_id": 42,
"conflicting_plan": {}
}
]
}
}{
"errors": {},
"status": "error",
"message": "The given data was invalid."
}{
"success": false,
"message": "Failed to update household",
"error": "Unable to complete household change."
}Authorizations
Passport bearer token authentication for legacy admin sessions.
Path Parameters
Record ID in the authenticated organization.
42
Record ID in the authenticated organization.
42
Body
Household relationship label.
Church, Employer, Employee, Friend, Director, Associate, Child, Parent, Grandparent, Grandchild, Relative, Spouse, Sibling, Designated Registrant, Registrant, Church Member, Partner, Organization, Member, Beneficiary, Sponsor "Church"
Household relationship label.
Church, Employer, Employee, Friend, Director, Associate, Child, Parent, Grandparent, Grandchild, Relative, Spouse, Sibling, Designated Registrant, Registrant, Church Member, Partner, Organization, Member, Beneficiary, Sponsor, null "Church"
Fallback donor for primary_relationship when household has no primary.
42
Make the new donor primary; send JSON true to activate.
false
Token from a 409 stewardship impact response; required when open plan impact is material.
4096"issued-household-impact-version"
Show child attributes
Show child attributes
Response
Add one donor to a household completed.
curl --request POST \
--url https://api.gratona.com/v1/admin/households/{household_id}/donors/{donor_id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"relationship": "Church",
"primary_relationship": "Church",
"primary": 42,
"make_primary": false,
"plan_impact_version": "issued-household-impact-version",
"plan_repairs": [
{
"plan_id": 42,
"action": "replace_action_donor",
"action_donor_id": 42,
"reason": "Household is being removed."
}
]
}
'import requests
url = "https://api.gratona.com/v1/admin/households/{household_id}/donors/{donor_id}"
payload = {
"relationship": "Church",
"primary_relationship": "Church",
"primary": 42,
"make_primary": False,
"plan_impact_version": "issued-household-impact-version",
"plan_repairs": [
{
"plan_id": 42,
"action": "replace_action_donor",
"action_donor_id": 42,
"reason": "Household is being removed."
}
]
}
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({
relationship: 'Church',
primary_relationship: 'Church',
primary: 42,
make_primary: false,
plan_impact_version: 'issued-household-impact-version',
plan_repairs: [
{
plan_id: 42,
action: 'replace_action_donor',
action_donor_id: 42,
reason: 'Household is being removed.'
}
]
})
};
fetch('https://api.gratona.com/v1/admin/households/{household_id}/donors/{donor_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/households/{household_id}/donors/{donor_id}",
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([
'relationship' => 'Church',
'primary_relationship' => 'Church',
'primary' => 42,
'make_primary' => false,
'plan_impact_version' => 'issued-household-impact-version',
'plan_repairs' => [
[
'plan_id' => 42,
'action' => 'replace_action_donor',
'action_donor_id' => 42,
'reason' => 'Household is being removed.'
]
]
]),
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/households/{household_id}/donors/{donor_id}"
payload := strings.NewReader("{\n \"relationship\": \"Church\",\n \"primary_relationship\": \"Church\",\n \"primary\": 42,\n \"make_primary\": false,\n \"plan_impact_version\": \"issued-household-impact-version\",\n \"plan_repairs\": [\n {\n \"plan_id\": 42,\n \"action\": \"replace_action_donor\",\n \"action_donor_id\": 42,\n \"reason\": \"Household is being removed.\"\n }\n ]\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/households/{household_id}/donors/{donor_id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"relationship\": \"Church\",\n \"primary_relationship\": \"Church\",\n \"primary\": 42,\n \"make_primary\": false,\n \"plan_impact_version\": \"issued-household-impact-version\",\n \"plan_repairs\": [\n {\n \"plan_id\": 42,\n \"action\": \"replace_action_donor\",\n \"action_donor_id\": 42,\n \"reason\": \"Household is being removed.\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.gratona.com/v1/admin/households/{household_id}/donors/{donor_id}")
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 \"relationship\": \"Church\",\n \"primary_relationship\": \"Church\",\n \"primary\": 42,\n \"make_primary\": false,\n \"plan_impact_version\": \"issued-household-impact-version\",\n \"plan_repairs\": [\n {\n \"plan_id\": 42,\n \"action\": \"replace_action_donor\",\n \"action_donor_id\": 42,\n \"reason\": \"Household is being removed.\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"message": "Donor successfully added to household",
"is_primary": false,
"donor": {
"id": 42,
"name": "Jane Doe",
"relationship": "Example"
},
"household": {
"id": 42,
"name": "Doe household",
"donor_ids": [
42
]
}
}{
"message": "Something went wrong. Please try again.",
"status": "error",
"error": "invalid_request",
"code": "DONOR_NOT_FOUND"
}{
"message": "Something went wrong. Please try again.",
"status": "error",
"error": "invalid_request",
"code": "DONOR_NOT_FOUND"
}{
"message": "Not Found",
"status": "error"
}{
"status": "error",
"code": "PLAN_IMPACT_CONFIRMATION_REQUIRED",
"message": "Review household plan impact before saving.",
"data": {
"material_impact": false,
"affected_plans": [
{
"id": 42,
"subject": {
"type": "donor",
"id": 42
},
"stage": "qualifying",
"status": "draft",
"period": {
"start": "2026-10-20",
"end": "2026-10-20"
},
"version": 2,
"action_donor_id": 42,
"scope_version": 2
}
],
"group_plans": [
"<string>"
],
"transfer_candidates": [
"<string>"
],
"scope_changes": [
{
"plan_id": 42,
"next_members": [
{
"member_type": "donor",
"member_id": 42,
"scope_role": "core"
}
],
"eligible_action_donor_ids": [
42
],
"invalid_open_move_ids": [
42
]
}
],
"eligible_action_donor_ids": [
42
],
"collisions": [
{
"plan_id": 42,
"conflicting_plan": {}
}
],
"authority_changes": [
"<string>"
],
"required_repairs": [
{
"plan_id": 42,
"choices": [
"replace_action_donor"
],
"eligible_action_donor_ids": [
42
],
"recommended_action_donor_id": 42
}
],
"proposed_member_donor_ids": [
42
],
"primary_contact_id": 42,
"plan_repairs": [
{
"plan_id": 42
}
],
"command": "household_membership",
"expires_at": "2026-10-20T12:00:00Z",
"impact_version": "issued-household-impact-version",
"conflicts": [
{
"plan_id": 42,
"conflicting_plan": {}
}
]
}
}{
"errors": {},
"status": "error",
"message": "The given data was invalid."
}{
"success": false,
"message": "Failed to update household",
"error": "Unable to complete household change."
}