> ## Documentation Index
> Fetch the complete documentation index at: https://docs.grainledger.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Pledge card imports

> Submit structured pledge card data and review it in Grain before creating pledges.

Use the pledge card import API when your church already has structured pledge data and does not need Grain to read scanned images. Each request creates a review batch, runs donor matching and duplicate checks, and returns a link where finance staff can review the rows.

The import does not create pledges automatically. Users with contribution management access review and confirm the submitted records in Grain just as they do with scanned pledge cards.

## Before importing from Rock

For exact person and fund routing, complete these steps first:

1. Install the current Grain plugin in Rock and let its scheduled job sync the financial account, campus, and person catalogs.
2. In Grain, map every Rock financial account that the workflow can submit. Map campus-specific child accounts as well as the parent fallback account.
3. Enable **Full directory** for the Rock integration and wait for the initial person sync to finish. This lets Grain resolve people who are in Rock but have never given.
4. Create a dedicated Digital Cards scan group in Grain if digital and scanned cards should be reviewed separately.

<Warning>
  A Rock campus ID does not choose a financial account. The Rock workflow must
  map each campus to the intended child `FinancialAccount.Id` and send it as
  `rockAccountId`. Use `defaultRockAccountId` for the parent or other fallback
  account when a card has no campus-specific account.
</Warning>

## Get valid routing IDs

Call the reference-data endpoint during setup and whenever the Rock account mappings change:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request GET \
  --url https://api.grainledger.com/api/v1/pledge-card-imports/reference-data \
  --header "Authorization: Bearer $GRAIN_ACCESS_TOKEN" \
  --header "X-Organization-Id: $GRAIN_ORGANIZATION_ID"
```

The response includes:

* `scanGroups`: Grain scan group IDs and their current campaign and default fund routing.
* `campaigns`: Active and draft Grain pledge campaign IDs.
* `funds`: Active Grain fund IDs for non-Rock integrations.
* `rockCampuses`: Exact Rock `Campus.Id` values recognized by Grain.
* `rockAccounts`: The complete Rock parent and child account catalog, including each account's path and mapping state.

Only submit a `rockAccountId` whose `mappingState` is `MAPPED` and `isActive` is `true`. `UNMAPPED`, `INACTIVE_FUND`, `AMBIGUOUS`, and `NOT_CATALOGED` accounts must be corrected in Grain first.

## Recommended Rock workflow payload

Send `POST /api/v1/pledge-card-imports` with your access token, organization ID, and a unique `Idempotency-Key`.

For a Rock digital pledge card workflow, send:

* `externalId`: The Rock workflow ID. Grain displays it during review and returns it in the item result.
* `rockPersonId`: The current Rock `Person.Id`. Grain uses it for exact identity routing instead of fuzzy donor matching.
* `rockAccountId`: The Rock `FinancialAccount.Id` selected by the workflow's campus-to-account mapping.
* `rockCampusId`: The Rock `Campus.Id`, when known. This provides campus context but does not select the account.
* `donorAddress2`: The second street-address line, when present.
* `scanGroupId`: The Grain ID for an existing Digital Cards scan group.
* `defaultRockAccountId`: The mapped parent or fallback Rock account for cards that do not include `rockAccountId`.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request POST \
  --url https://api.grainledger.com/api/v1/pledge-card-imports \
  --header "Authorization: Bearer $GRAIN_ACCESS_TOKEN" \
  --header "X-Organization-Id: $GRAIN_ORGANIZATION_ID" \
  --header "Idempotency-Key: rock-workflow-run-882145" \
  --header "Content-Type: application/json" \
  --data '{
    "scanGroupId": "CMRC90",
    "defaultRockAccountId": "101",
    "cards": [
      {
        "externalId": "882145",
        "rockPersonId": "34567",
        "rockAccountId": "204",
        "rockCampusId": "3",
        "donorFirstName": "Sarah",
        "donorLastName": "Johnson",
        "donorEmail": "sarah@example.org",
        "donorAddress": "100 Church Street",
        "donorAddress2": "Apartment 4",
        "donorCity": "Nashville",
        "donorState": "TN",
        "donorZip": "37201",
        "amount": 12000,
        "frequency": "MONTHLY",
        "startDate": "2027-01-01",
        "endDate": "2027-12-31"
      }
    ]
  }'
```

<Note>
  When `scanGroupId` is supplied, Grain inherits the group's campaign. Do not
  send `campaignId`. A supplied `defaultRockAccountId` overrides the group's
  default fund for that batch, and a card-level `rockAccountId` overrides the
  request default for that card.
</Note>

## Create a new scan group

Omit `scanGroupId` and provide `name`, either `defaultRockAccountId` or `defaultFundId`, and your `cards`. Grain creates a new scan group and its first batch.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --request POST \
  --url https://api.grainledger.com/api/v1/pledge-card-imports \
  --header "Authorization: Bearer $GRAIN_ACCESS_TOKEN" \
  --header "X-Organization-Id: $GRAIN_ORGANIZATION_ID" \
  --header "Idempotency-Key: pledge-import-2027-digital-001" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "2027 digital pledge cards",
    "defaultRockAccountId": "101",
    "campaignId": "cm890jkl123mno",
    "cards": [
      {
        "externalId": "882146",
        "rockPersonId": "34568",
        "donorFirstName": "Alex",
        "donorLastName": "Rivera",
        "amount": 6000,
        "frequency": "MONTHLY"
      }
    ]
  }'
```

Use `defaultFundId` and card-level `fundId` only when the caller already has Grain fund IDs. Do not send `defaultFundId` with `defaultRockAccountId`, or `fundId` with `rockAccountId`.

## Add to an existing scan group

Pass `scanGroupId` to append cards as a new batch inside a group created in Grain or from a prior import.

<Note>
  Use the ID at the end of the scan group review URL, not the display name. For
  example, if the URL ends in `/revenue/pledge-card-scan-groups/CMRC90`, send
  `"scanGroupId": "CMRC90"`.
</Note>

A request can contain between 1 and 500 cards and must be 2 MB or smaller.

## Retry safely

Use a new `Idempotency-Key` for each logical import. A Rock workflow run ID is a good basis for this value. If a request times out or its result is unclear, retry the exact request body with the same key. Grain returns the existing import instead of creating duplicate review rows.

Reusing a key with a different request body returns `409 Conflict`. If the original request is still processing, wait briefly and retry with the same key.

## Review import results

The response contains:

* `reviewUrl`: Opens the review group in Grain.
* `cardsImported`: Rows added to the review workflow.
* `cardsSkipped`: Exact duplicates retained as skipped audit rows.
* `cardsWithIssues`: Rows with missing, invalid, or unresolved business data that need attention.
* `items`: Per-card results mapped back through `externalId` when supplied, including Rock identity and account status.

An exact repeated `externalId` or card body is retained as a `SKIPPED` audit row instead of rejecting the entire request. Missing donor names, amounts, or dates do not discard an otherwise valid row. Grain imports the row with an issue so finance staff can correct it during review.

When `rockPersonId` is present, Grain does not fall back to fuzzy matching. The row reports `MATCHED`, `PENDING`, `NOT_FOUND`, or `CONFLICT` so staff can resolve the exact Rock identity without creating an accidental duplicate person.

The secured JSON audit artifact is retained by Grain, but it is not presented or synchronized as a scanned pledge card document. The visible `externalId` is the link back to the originating Rock workflow.
