curl --request POST \
--url https://api.grainledger.com/api/v1/pledge-card-imports \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"cards": [
{
"externalId": "882145",
"rockPersonId": "34567",
"rockAccountId": "204",
"rockCampusId": "3",
"donorFirstName": "Sarah",
"donorLastName": "Johnson",
"donorEmail": "sarah@example.org",
"amount": 12000,
"frequency": "MONTHLY",
"startDate": "2027-01-01",
"endDate": "2027-12-31"
}
]
}
'const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
cards: [
{
externalId: '882145',
rockPersonId: '34567',
rockAccountId: '204',
rockCampusId: '3',
donorFirstName: 'Sarah',
donorLastName: 'Johnson',
donorEmail: 'sarah@example.org',
amount: 12000,
frequency: 'MONTHLY',
startDate: '2027-01-01',
endDate: '2027-12-31'
}
]
})
};
fetch('https://api.grainledger.com/api/v1/pledge-card-imports', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.grainledger.com/api/v1/pledge-card-imports"
payload = { "cards": [
{
"externalId": "882145",
"rockPersonId": "34567",
"rockAccountId": "204",
"rockCampusId": "3",
"donorFirstName": "Sarah",
"donorLastName": "Johnson",
"donorEmail": "sarah@example.org",
"amount": 12000,
"frequency": "MONTHLY",
"startDate": "2027-01-01",
"endDate": "2027-12-31"
}
] }
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"scanGroupId": "<string>",
"batchId": "<string>",
"reviewUrl": "<string>",
"idempotentReplay": true,
"sourceFileAttachmentId": "<string>",
"cardsImported": 1,
"cardsSkipped": 1,
"cardsWithIssues": 1,
"duplicateCards": 1,
"items": [
{
"itemId": "<string>",
"externalId": "<string>",
"rockPersonId": "<string>",
"rockAccountId": "<string>",
"sourceIdentityStatus": "PENDING",
"status": "EXTRACTED",
"issue": "<string>",
"duplicateOfId": "<string>"
}
]
}{
"code": "forbidden",
"message": "You do not have access to this organization.",
"error": "You do not have access to this organization.",
"details": {
"fields": [
{
"field": "amount",
"code": "invalid_type"
}
]
}
}{
"code": "forbidden",
"message": "You do not have access to this organization.",
"error": "You do not have access to this organization.",
"details": {
"fields": [
{
"field": "amount",
"code": "invalid_type"
}
]
}
}{
"code": "forbidden",
"message": "You do not have access to this organization.",
"error": "You do not have access to this organization.",
"details": {
"fields": [
{
"field": "amount",
"code": "invalid_type"
}
]
}
}{
"code": "forbidden",
"message": "You do not have access to this organization.",
"error": "You do not have access to this organization.",
"details": {
"fields": [
{
"field": "amount",
"code": "invalid_type"
}
]
}
}{
"code": "forbidden",
"message": "You do not have access to this organization.",
"error": "You do not have access to this organization.",
"details": {
"fields": [
{
"field": "amount",
"code": "invalid_type"
}
]
}
}{
"code": "forbidden",
"message": "You do not have access to this organization.",
"error": "You do not have access to this organization.",
"details": {
"fields": [
{
"field": "amount",
"code": "invalid_type"
}
]
}
}{
"code": "forbidden",
"message": "You do not have access to this organization.",
"error": "You do not have access to this organization.",
"details": {
"fields": [
{
"field": "amount",
"code": "invalid_type"
}
]
}
}{
"code": "forbidden",
"message": "You do not have access to this organization.",
"error": "You do not have access to this organization.",
"details": {
"fields": [
{
"field": "amount",
"code": "invalid_type"
}
]
}
}{
"code": "forbidden",
"message": "You do not have access to this organization.",
"error": "You do not have access to this organization.",
"details": {
"fields": [
{
"field": "amount",
"code": "invalid_type"
}
]
}
}Import pledge card data for review
Creates a pledge card review group from structured data. Rows enter the same donor matching and review workflow as scanned cards; this request does not create pledges.
curl --request POST \
--url https://api.grainledger.com/api/v1/pledge-card-imports \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"cards": [
{
"externalId": "882145",
"rockPersonId": "34567",
"rockAccountId": "204",
"rockCampusId": "3",
"donorFirstName": "Sarah",
"donorLastName": "Johnson",
"donorEmail": "sarah@example.org",
"amount": 12000,
"frequency": "MONTHLY",
"startDate": "2027-01-01",
"endDate": "2027-12-31"
}
]
}
'const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
cards: [
{
externalId: '882145',
rockPersonId: '34567',
rockAccountId: '204',
rockCampusId: '3',
donorFirstName: 'Sarah',
donorLastName: 'Johnson',
donorEmail: 'sarah@example.org',
amount: 12000,
frequency: 'MONTHLY',
startDate: '2027-01-01',
endDate: '2027-12-31'
}
]
})
};
fetch('https://api.grainledger.com/api/v1/pledge-card-imports', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.grainledger.com/api/v1/pledge-card-imports"
payload = { "cards": [
{
"externalId": "882145",
"rockPersonId": "34567",
"rockAccountId": "204",
"rockCampusId": "3",
"donorFirstName": "Sarah",
"donorLastName": "Johnson",
"donorEmail": "sarah@example.org",
"amount": 12000,
"frequency": "MONTHLY",
"startDate": "2027-01-01",
"endDate": "2027-12-31"
}
] }
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"scanGroupId": "<string>",
"batchId": "<string>",
"reviewUrl": "<string>",
"idempotentReplay": true,
"sourceFileAttachmentId": "<string>",
"cardsImported": 1,
"cardsSkipped": 1,
"cardsWithIssues": 1,
"duplicateCards": 1,
"items": [
{
"itemId": "<string>",
"externalId": "<string>",
"rockPersonId": "<string>",
"rockAccountId": "<string>",
"sourceIdentityStatus": "PENDING",
"status": "EXTRACTED",
"issue": "<string>",
"duplicateOfId": "<string>"
}
]
}{
"code": "forbidden",
"message": "You do not have access to this organization.",
"error": "You do not have access to this organization.",
"details": {
"fields": [
{
"field": "amount",
"code": "invalid_type"
}
]
}
}{
"code": "forbidden",
"message": "You do not have access to this organization.",
"error": "You do not have access to this organization.",
"details": {
"fields": [
{
"field": "amount",
"code": "invalid_type"
}
]
}
}{
"code": "forbidden",
"message": "You do not have access to this organization.",
"error": "You do not have access to this organization.",
"details": {
"fields": [
{
"field": "amount",
"code": "invalid_type"
}
]
}
}{
"code": "forbidden",
"message": "You do not have access to this organization.",
"error": "You do not have access to this organization.",
"details": {
"fields": [
{
"field": "amount",
"code": "invalid_type"
}
]
}
}{
"code": "forbidden",
"message": "You do not have access to this organization.",
"error": "You do not have access to this organization.",
"details": {
"fields": [
{
"field": "amount",
"code": "invalid_type"
}
]
}
}{
"code": "forbidden",
"message": "You do not have access to this organization.",
"error": "You do not have access to this organization.",
"details": {
"fields": [
{
"field": "amount",
"code": "invalid_type"
}
]
}
}{
"code": "forbidden",
"message": "You do not have access to this organization.",
"error": "You do not have access to this organization.",
"details": {
"fields": [
{
"field": "amount",
"code": "invalid_type"
}
]
}
}{
"code": "forbidden",
"message": "You do not have access to this organization.",
"error": "You do not have access to this organization.",
"details": {
"fields": [
{
"field": "amount",
"code": "invalid_type"
}
]
}
}{
"code": "forbidden",
"message": "You do not have access to this organization.",
"error": "You do not have access to this organization.",
"details": {
"fields": [
{
"field": "amount",
"code": "invalid_type"
}
]
}
}Authorizations
Organization API key created in Grain Settings → API. The key determines the organization and is limited to its assigned resource scopes. Write scopes imply the corresponding read scope.
Headers
Required with OAuth access tokens for resource requests. Omit only for GET /api/v1/organizations. Optional with organization API keys; if supplied with an API key, it must match the key's organization.
A unique key for this import request. Retrying the same body with the same key returns the original import without creating duplicate rows.
1 - 255Body
1 - 500 elementsHide child attributes
Hide child attributes
128"card-1001"
Current Rock Person.Id. Exact identity routing bypasses fuzzy donor matching.
128100"Sarah"
100"Johnson"
255"sarah@example.org"
50"+1 615 555 0100"
255"100 Church Street"
Second street-address line. Grain joins both lines with one space for donor matching while preserving both in the import artifact.
255"Suite 200"
100"Nashville"
100"TN"
20"37201"
x >= 012000
ONE_TIME, WEEKLY, BIWEEKLY, MONTHLY, QUARTERLY, ANNUALLY "MONTHLY"
Optional per-card Grain fund ID. Do not send with rockAccountId.
Exact Rock financial account ID for this card. Do not send with fundId.
128Informational label shown during review. It is never used for fund routing or fallback.
200Legacy internal Grain campus ID. Do not send with rockCampusId.
Exact Rock campus ID resolved through the integration campus catalog. Campus does not select a financial account.
128Legacy campus label used by the existing review matcher. Use rockCampusId for exact routing.
200"2027-01-01"
"2027-12-31"
2000Existing pledge card scan group ID. When set, cards are added as a new batch in that group. campaignId is inherited; a supplied request default fund or Rock account overrides the group's default.
Required when scanGroupId is omitted. Becomes the new scan group name.
1 - 100"2027 annual pledge cards"
Grain fund used when a card has no override. Required when scanGroupId is omitted unless defaultRockAccountId is supplied.
Exact Rock financial account used when a card has no override. Do not send with defaultFundId.
Optional Grain pledge campaign ID. Ignored when scanGroupId is set; the group's campaign is used instead.
ADULT, STUDENT Response
Pledge card data imported for review
Secured audit artifact ID; not a scanned card attachment
x >= 0x >= 0x >= 0x >= 0Hide child attributes
Hide child attributes
PENDING, MATCHED, CONFLICT, NOT_FOUND EXTRACTED, SKIPPED