Import churches reliably

Use POST /v1/churches/import to register a directory. Each batch accepts up to 500 rows and returns a result for each row. An HTTP success alone does not mean every church was imported.

Give each church a stable reference

Your existing church ID is the best reference: it stays the same across retries, renamed churches, and future exports. Keep it unique within your partner account. Reusing a reference returns the existing church, even when the request uses a new idempotency key; it does not update the church's data.

curl --fail-with-body -X POST "$ROSTER_BASE_URL/v1/churches/import" \
  -H "Authorization: Bearer $ROSTER_KEY" \
  -H "Idempotency-Key: directory-2026-09-batch-001" \
  -H "Content-Type: application/json" \
  --data '{"churches":[
    {"name":"Integration Church A","reference":"church-001"},
    {"name":"Integration Church B","reference":"church-002"}
  ]}'

Checkpoint every row

The response is 200 with one entry in results per row, in input order. Each entry carries the row's index (from 0) and its status: created, replayed or rejected. A created or replayed entry carries church; a rejected entry carries detail instead:

{
  "results": [
    {
      "index": 0,
      "status": "created",
      "church": {
        "id": "ch_8f3a2b1c9d4e",
        "name": "Integration Church A",
        "created_at": "2026-10-09T18:02:11.000Z",
        "reference": "church-001"
      }
    },
    {
      "index": 1,
      "status": "rejected",
      "detail": "name is required"
    }
  ]
}

For each entry in results, use its index to recover the source row and branch on results[].status. Store the returned church.id for created and replayed results. Record the detail for a rejected result and route that row for correction. Preserve your original input and batch identifier until every row is resolved.

Store two more things from this response, because no read returns them: church.bank_verification, present when the row carried bank_account (see the next section), and church.linked, present when another platform registered the church first (see When the EIN matches a church Roster already has).

For a directory of about 500 churches, start with a small batch to validate mapping, then increase the batch size if response times are acceptable. The 500-row limit is an API limit, not a throughput guarantee. A practical initial batch size is 25 rows.

Situation Next action
Connection drops before you receive the response Retry the identical batch, in the identical order, with the same key
Some rows are rejected Correct those rows and submit a new batch with a new key, retaining their references; a changed batch under the old key gets 409
A church already exists Keep its returned ID; do not create a new reference to bypass deduplication
Request returns 429 Wait for Retry-After; resume from the stored batch
A reference maps to an unexpected church (the returned reference is not the one you sent) Stop that row and investigate your source mapping before opening donor sessions; see When the EIN matches a church Roster already has

Batch identity includes the row index. Never reorder, insert, or remove rows while reusing a batch key.

POST /v1/church_consents records the document, version, acceptance time and method. Identify the church with one of church_id or church_reference. If consent is recorded before import, use the same reference on import so the records can be linked. Record an actual acceptance; an import is not consent.

POST /v1/churches/{id}/bank-account supplies account information in the request body. Its response carries bank_verification; the next section says what each status asks of you. A registration response does not confirm that funds can be paid out. Keep bank details out of logs, analytics, URLs and AI chat transcripts.

When a church is ready for donors (payout_ready)

Importing a church does not make it ready for paycheck gifts. Roster must verify two things first:

  1. The church's legal details. Submit them with POST /v1/churches/{id}/verification; see Complete a church's legal setup. In the sandbox, a test EIN goes only in this request: sent in ein on import, its row comes back rejected. GET /sandbox/fixtures lists every test value.
  2. Its receiving account. Send bank_account with the church on import, or later with POST /v1/churches/{id}/bank-account.

Store bank_verification.status from the response that registered the account: the create or import response (church.bank_verification) or the POST /v1/churches/{id}/bank-account response (bank_verification). GET /v1/churches and GET /v1/churches/{id} do not return it; read receiving_account_verified there.

bank_verification.status What to do
verified or already_verified Nothing. The account is confirmed.
rejected The numbers do not identify a valid account. Ask the church for the right numbers and send them with POST /v1/churches/{id}/bank-account and a new Idempotency-Key.
in_review Nothing to send. A Roster reviewer decides; you are not asked for anything else. Roster does not contact the church about this review. In the sandbox nobody reviews: register an account with another test ending to continue.
pending Production only, for now. Nothing to send: the account is recorded, and no payout flows until Roster confirms it.

Until Roster verifies both, payout_ready stays false, and a donor who opens the flow for that church is told that paycheck giving isn't enabled for it yet. Roster does not ask you for anything else through the API.

Turn a church's giving button on when its payout_ready is true on GET /v1/churches/{id}. Where church.onboarded is sent, it announces that moment once per church, the first time the church becomes ready; read payout_ready either way:

  • A church that stops being ready later sends no event. Leave the giving button on when payout_ready later turns false: the hosted flow itself tells new donors when the church cannot take new gifts. Show the church as in review, with no reason, in your admin: the words Roster uses with the church; see Church status after verification for what false means on a church that was already verified.
  • A church that was ready before Roster started sending church.onboarded never sends it.
  • For a church that another platform registered first, that platform receives church.onboarded.
  • Today Roster sends church.onboarded in the sandbox only. In production, read payout_ready.

Recording consent with POST /v1/church_consents does not change payout_ready.

When the church receives its payouts

Some churches want one deposit day a week instead of one per weekday. Send payout_schedule with the church on import, or later with POST /v1/churches/{id}/payout-schedule:

  • daily, the default: each donation goes out at the first weekday cutoff after it is ready, as described in From a received deposit to an executed transfer.
  • weekly: a donation deposited any day goes out on the next Monday, or on the next business day when that Monday is a bank holiday. A donation deposited on Monday waits for the following Monday.

Only the day changes. Each donation is still its own transfer, and the money waits in the donor's account until the church's day. A change applies from the next cutoff, including to donations already waiting. GET /v1/churches/{id} returns the schedule in effect. Like the receiving account, only the platform that registered the church first can set it: for a church you reached through linked: existing_church, the value is ignored on import and the route returns 404.

When the EIN matches a church Roster already has

An ein that matches a church Roster already has never creates a second church. The create or import response tells you whose church it is:

  • linked: existing_church is present: the ein you sent matched a church Roster already had, registered first by another platform. You get a channel to that church, not a new record. The settlement account and the church's legal details stay with that platform: a bank_account you send with that church is ignored and bank_verification is absent, and POST /v1/churches/{id}/bank-account and POST /v1/churches/{id}/verification return 404 for it.
  • No linked, and the row comes back replayed with a church id you already stored and the reference you registered first: two of your own source rows share an EIN. linked never appears on your own churches. Nothing new was created and you remain the church's owner: its account and legal details are still yours to send. Resolve the duplicate in your source.

Import acceptance check

To reconcile against Roster, list your churches with GET /v1/churches: it returns up to 200 churches per page, ordered by id. Repeat the call with cursor set to the next_cursor you received until next_cursor is absent. Each list item carries id, name, created_at and payout_ready, plus reference when you registered one. GET /v1/churches/{id} adds consents and receiving_account_verified. Keep linked and bank_verification from the create or import response (and bank_verification from POST /v1/churches/{id}/bank-account): no read returns them.

Before inviting donors, account for every source church: imported, replayed, or rejected with an owner and a reason. Turn a church's giving button on only once its payout_ready is true. Confirm that your system retains the mapping after a process restart, that retrying an interrupted batch creates no duplicates, and that one malformed row does not silently disappear.