Remove a donor from a household
Detaches the member and preserves its donor record. Removing the primary requires a new_primary_contact_id from this household or a force key; the current controller checks force presence. Replacement must remain in the proposed member set. A missing confirmation returns 422 with requires_confirmation/is_primary_contact. 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 DELETE \
--url https://api.gratona.com/v1/admin/households/{household_id}/donors/{donor_id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"force": false,
"new_primary_contact_id": 42,
"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 = {
"force": False,
"new_primary_contact_id": 42,
"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.delete(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'DELETE',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
force: false,
new_primary_contact_id: 42,
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 => "DELETE",
CURLOPT_POSTFIELDS => json_encode([
'force' => false,
'new_primary_contact_id' => 42,
'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 \"force\": false,\n \"new_primary_contact_id\": 42,\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("DELETE", 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.delete("https://api.gratona.com/v1/admin/households/{household_id}/donors/{donor_id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"force\": false,\n \"new_primary_contact_id\": 42,\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::Delete.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"force\": false,\n \"new_primary_contact_id\": 42,\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 removed from household",
"was_primary": false,
"donor": {
"id": 42,
"name": "Jane Doe"
},
"household": {
"id": 42,
"name": "Doe household",
"donor_ids": [
42
],
"primary_contact_id": 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
Supply to acknowledge removing the primary without replacement.
false
Another active member to retain as primary.
42
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
Remove a donor from a household completed.
curl --request DELETE \
--url https://api.gratona.com/v1/admin/households/{household_id}/donors/{donor_id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"force": false,
"new_primary_contact_id": 42,
"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 = {
"force": False,
"new_primary_contact_id": 42,
"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.delete(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'DELETE',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
force: false,
new_primary_contact_id: 42,
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 => "DELETE",
CURLOPT_POSTFIELDS => json_encode([
'force' => false,
'new_primary_contact_id' => 42,
'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 \"force\": false,\n \"new_primary_contact_id\": 42,\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("DELETE", 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.delete("https://api.gratona.com/v1/admin/households/{household_id}/donors/{donor_id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"force\": false,\n \"new_primary_contact_id\": 42,\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::Delete.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"force\": false,\n \"new_primary_contact_id\": 42,\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 removed from household",
"was_primary": false,
"donor": {
"id": 42,
"name": "Jane Doe"
},
"household": {
"id": 42,
"name": "Doe household",
"donor_ids": [
42
],
"primary_contact_id": 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."
}