MailForce API

A read-only REST API for retrieving donation records and the donor information attached to them. Designed for partners who need their own copy of the data — for analytics, archival, CRM sync, or building reports beyond what MailForce ships.

Overview

The API exposes one resource today: donations. A donation record includes the amount, the payment method, the campaign that prompted it, the donor's name and address, and the contact details we have on file. Each call returns JSON. Filtering by date is the primary access pattern.

If you previously consumed our nightly CSV export, the /donations/caging endpoint returns the same fields with the same column names, so migrating is mostly a matter of switching from a file pull to an HTTP call.

Which donations are returned

The API exposes finalized donation records only. A donation becomes visible once it has been processed by our back-office team and reached one of three terminal states:

StatusMeaning
Submitted The donation has been reconciled against a deposit and submitted to the bank, but the deposit receipt is not yet on file.
Deposited The donation has been reconciled, submitted, and the bank deposit receipt is on file.
Completed A correspondence-only record — for example, a mail piece received without an enclosed gift. These records do not pass through bank reconciliation; they reach Completed the moment their batch is attached to a deposit.

Records still in-flight inside MailForce (statuses Open or Reconciled) and records that have been deleted are not returned. This means the same record may appear in a later sync than your first one — for example, a donation reconciled on Monday may only reach Submitted on Tuesday. Use the updated_since filter to pick up records that have changed since your previous run.

To include or isolate correspondence-only entries, use the type filter on the list endpoint: type=donor returns only records with a payment method, type=non_donor returns only correspondence-only records, and omitting the filter returns both.

Quickstart

  1. Email your MailForce administrator and request API credentials. They will issue you a client ID, a client secret, and one or both of the available scopes.
  2. Exchange those credentials for a short-lived access token by POSTing to /oauth/token (see Authentication).
  3. Call any endpoint with the token in an Authorization: Bearer <token> header. Try https://ign.mailforceapp.com/api/v1/donations/fields first to confirm everything is wired up — it returns the list of fields your token can see.
  4. Pull data. The most common pattern is a single-day fetch:
    curl -H "Authorization: Bearer $TOKEN" \
      'https://ign.mailforceapp.com/api/v1/donations?date=2026-04-25'
  5. Tokens expire. When a request returns 401, repeat step 2 to obtain a fresh one. (Tokens currently last 30 days, but rely on the 401 response rather than a hard-coded TTL.)

Authentication

The API uses the OAuth 2.0 Client Credentials flow. There is no user login — your client ID and secret authenticate the calling application directly.

Exchange credentials for a token

POST https://ign.mailforceapp.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&scope=read-donations read-pii

The response contains an access_token. Send it on every API request:

Authorization: Bearer YOUR_ACCESS_TOKEN
Treat your client secret like a password. Anyone with the secret can pull donor data. Store it in a secret manager, never commit it to source control, and rotate it via your administrator if you suspect it has leaked.

Scopes

Scopes determine which fields you receive. We issue two:

ScopeWhat it grants
read-donations Read donation, finder, batch, and payment data (no PII)
read-pii Read donor PII: person, address, contacts, metadata, and historical (old_*) values

If your token only carries read-donations, fields that require read-pii (names, addresses, phone numbers, email) are silently omitted from responses. Asking for one of those fields explicitly via the optional_fields parameter returns a 403 with the offending field names listed.

Rate limits

Each access token gets its own rate-limit bucket. Exceeding a limit returns 429 Too Many Requests with a Retry-After header indicating how long to wait.

LimiterLimitApplies to
Standard 60 requests per minute, per token Applies to every API endpoint.
Bulk export 10 requests per minute and 200 requests per day, per token Applies on top of the standard limit when calling the /donations/caging endpoint, since each call can return a full day or month of records.

Pagination

List endpoints return at most 50 records per page by default (maximum 200, configurable via per_page). When more results exist, a cursor is included in the response so you can fetch the next page. There are no offset / page-number parameters — cursor pagination is stable even if records are added or removed while you page through results.

Response shape

Every list response includes a meta block:

{
  "data": [ /* ... up to per_page records ... */ ],
  "meta": {
    "fields_returned": ["id", "finder", "donation_amount", "..."],
    "per_page": 50,
    "next_cursor": "eyJjcmVhdGVkX2F0Ijoi…",
    "prev_cursor": null
  }
}
  • meta.next_cursor — opaque token for the next page. null when you are on the last page (no more results).
  • meta.prev_cursor — token to step back to the previous page. null on the first page.
  • meta.per_page — echoes the page size in effect for this response.
  • meta.fields_returned — the fields included in data, after applying your scopes and any optional_fields.

Fetching the next page

Take the next_cursor value from the previous response and pass it as the cursor query parameter. Do not modify or decode the cursor — treat it as an opaque string. Keep your other filters (date_from, date_to, optional_fields, etc.) on every request; they are not encoded into the cursor.

curl -H "Authorization: Bearer $TOKEN" \
  'https://ign.mailforceapp.com/api/v1/donations?date_from=2026-04-01&date_to=2026-04-25&cursor=eyJjcmVhdGVkX2F0Ijoi…'

Worked example: pulling every donation in a window

params = { date_from: "2026-04-01", date_to: "2026-04-25", per_page: 200 }
all = []
loop:
    response = GET /api/v1/donations?{params}
    all.append(response.data)
    if response.meta.next_cursor is null:
        break
    params.cursor = response.meta.next_cursor
return all

Tips

  • Increase per_page up to 200 for bulk pulls — fewer requests, fewer round trips, friendlier on rate limits.
  • Going backwards: pass meta.prev_cursor as cursor to step back. Only useful when displaying paged UI; bulk syncs always go forward until next_cursor is null.
  • Stable ordering: results are ordered by your chosen date_field (default created_at) in your chosen order (default asc), with id as a tiebreaker. The cursor encodes this position, so concurrent inserts will appear in subsequent pages without skipping or duplicating already-paged records. Keep date_field and order consistent across paginated requests in the same loop — changing them mid-loop will produce a fresh ordering and your cursor will no longer be valid.
  • For incremental sync, pair pagination with the updated_since filter: pass an ISO-8601 timestamp from your last successful sync, then page through any records that have changed since.
  • Rate-limit awareness: each page is one request against the standard limit (60/min). The /donations/caging endpoint also counts against the bulk-export limit (10/min, 200/day) — plan large pulls accordingly.

Donations

Read donations and the donor information attached to them.

GET/api/v1/donations

Returns a paginated list of donations. Use the date filters to retrieve donations entered on a particular day or within a window.

Parameters

date
A single calendar day, formatted YYYY-MM-DD (for example, 2026-04-25). Returns donations entered on that day (or, if you pass `date_field=mail_date`, donations whose campaign was mailed on that day).
date_from, date_to
A date range, inclusive. The window cannot exceed 31 days. Subject to the same `date_field` toggle as `date`.
date_field
Which column the `date`, `date_from`, and `date_to` filters apply to. `created_at` (default) returns donations by the day they were recorded in MailForce. `mail_date` returns donations by the day the campaign was mailed. Results are also ordered by this column.
order
Sort direction for the date column: `asc` (default, oldest first) or `desc` (newest first). The donation's ID is always used as a tiebreaker so cursor pagination remains stable when multiple records share the same date.
updated_since
An ISO-8601 timestamp. Returns only donations whose record has changed at or after this time. Useful for incremental sync.
mail_code
Filter to donations that responded to a specific campaign code.
payment_method
Filter by payment type (cash, check, credit-card, etc.).
type
Filter by record type. `donor` returns donations with a payment method (real financial transactions). `non_donor` returns correspondence-only records (e.g., a mail piece received without an enclosed gift). Omit to return both.
donor_id, finder_id, status_id
Filter to a specific donor, finder, or workflow status.
optional_fields
Comma-separated list of optional-tier field names to include in the response (or "*" for everything your scopes allow). See the Field reference below.
per_page
Page size. Default 50, maximum 200.
cursor
Opaque pagination cursor returned in the previous response's meta.next_cursor. Omit on the first page.
format
Set to "caging" to receive keys in the same column-name format as our reconciliation export. Default is snake_case keys.

Example

curl -H "Authorization: Bearer $TOKEN" \
  'https://ign.mailforceapp.com/api/v1/donations?date=2026-04-25'

GET/api/v1/donations/caging

Returns the same fields as our internal reconciliation export, keyed by the export's column names. Use this if you are migrating from the nightly file feed and want the response to match its column headers exactly. Requires either a `date` or a `date_from` + `date_to` window. Carries an additional, lower rate limit on top of the standard one.

Parameters

date or date_from + date_to
Required. Same semantics as /donations.
optional_fields, per_page, cursor
Same as /donations.

Example

curl -H "Authorization: Bearer $TOKEN" \
  'https://ign.mailforceapp.com/api/v1/donations/caging?date=2026-04-25'

GET/api/v1/donations/{id}

Retrieves a single donation by its unique identifier.

Parameters

id
The donation's UUID, returned in the `id` field of every list response.
optional_fields
Same as /donations.
format
Same as /donations.

Example

curl -H "Authorization: Bearer $TOKEN" \
  'https://ign.mailforceapp.com/api/v1/donations/9c2d…'

GET/api/v1/donations/fields

Lists every field your token is allowed to receive, generated live from the server. Useful for verifying which optional fields you can request and what the current default response shape is. Returned data matches the Field reference table below.

Example

curl -H "Authorization: Bearer $TOKEN" \
  'https://ign.mailforceapp.com/api/v1/donations/fields'

Field reference

Every field a donation response can include is listed below. The list is generated live — what you see here matches what the API actually returns today.

  • Scope — which scope your token must hold to receive this field.
  • Tierdefault fields are returned automatically; optional fields require ?optional_fields=name1,name2 (or ?optional_fields=* to include everything your scopes allow).
  • Caging column — the column header used when calling with format=caging, matching our internal reconciliation export.
Field Scope Tier Caging column Description
id any default Donation UUID
created_at any default Donation creation timestamp (ISO-8601)
updated_at any default Donation last-update timestamp (ISO-8601)
finder read-donations default Finder Finder reference number assigned to the donor
mail_code read-donations default Mail_Code Mail code from finder; defaults to "WM" (white mail) when absent
donation_amount read-donations default Donation_Amount Amount given. Most donations are a single number; gifts split across funds may be returned as an array.
mail_date read-donations default Mail_Date Effective mail date for reporting purposes, formatted m/d/Y. See glossary.
payment_type read-donations default Payment_Type Payment method (cash, check, credit-card, etc.)
fulfillment read-donations optional Fulfillment Donor fulfillment preference
quarterly read-donations optional Quarterly "T" when donor mail notification cadence is quarterly, otherwise empty
yearly read-donations optional Yearly "T" when donor mail notification cadence is annually, otherwise empty
biyearly read-donations optional BiYearly "T" when donor mail notification cadence is biyearly, otherwise empty
petition read-donations optional Petition Donor petition flag
planned_giving read-donations optional Planned_Giving Donor planned giving flag
title read-pii default Title Person title/prefix from metadata
first read-pii default First Person first name
middle read-pii default Middle Person middle name
last read-pii default Last Person last name
suffix read-pii default Suffix Person name suffix from metadata
spouse read-pii optional Spouse Spouse person ID, if linked
company read-pii default Company Company name from metadata
street_address read-pii default Street_Address Street address (default address)
city read-pii default City City (default address)
state read-pii default State State (default address)
zip read-pii default Zip ZIP/postal code (default address)
optional_address1 read-pii optional Optional_Address1 Optional address line 1 (reserved; currently empty in caging report)
optional_address2 read-pii optional Optional_Address2 Optional address line 2 (reserved; currently empty in caging report)
home_phone read-pii optional Home_Phone Home phone contact value
business_phone read-pii optional Business_Phone Business phone contact value
employer read-pii optional Employer Employer (reserved; currently empty in caging report)
occupation read-pii optional Occupation Occupation (reserved; currently empty in caging report)
email read-pii optional E_Mail Email contact value
deceased read-pii default Deceased Deceased flag (reserved; currently empty)
nomail read-pii default Nomail No-mail flag (reserved; currently empty)
norent read-pii default Norent No-rent flag (reserved; currently empty)
nocall read-pii default NoCall No-call flag (reserved; currently empty)
noemail read-pii default NoEmail No-email flag (reserved; currently empty)
old_title read-pii optional Old_Title Title before the same-day "Update Person" activity
old_first_name read-pii optional Old_First_Name First name before the same-day "Update Person" activity
old_middle_name read-pii optional Old_Middle_Name Middle name before the same-day "Update Person" activity
old_last_name read-pii optional Old_Last_Name Last name before the same-day "Update Person" activity
old_suffix read-pii optional Old_Suffix Suffix before the same-day "Update Person" activity
old_company read-pii optional Old_Company Company before the same-day "Update Person" activity
old_street read-pii optional Old_Street Street before the same-day "Update Address" activity
old_address_line2 read-pii optional Old_Address_Line2 Address line 2 before the same-day "Update Address" activity
old_city read-pii optional Old_City City before the same-day "Update Address" activity
old_state read-pii optional Old_State State before the same-day "Update Address" activity
old_zip read-pii optional Old_Zip ZIP/postal code before the same-day "Update Address" activity
birth_date read-pii optional Birth_Date Birth date (reserved; currently empty in caging report)
spouse_birth_date read-pii optional Spouse_Birth_Date Spouse birth date (reserved; currently empty in caging report)
spouse_email read-pii optional Spouse_Email Spouse email (reserved; currently empty in caging report)
salutation read-pii optional Salutation Formal salutation (reserved; currently empty in caging report)
informal_salutation read-pii optional Informal_Salutation Informal salutation (reserved; currently empty in caging report)
mobile_phone read-pii optional Mobile_Phone Mobile phone contact value (sourced from contacts.name="phone")
fax read-pii optional Fax Fax (reserved; currently empty in caging report)

Glossary

Terms used throughout the API and the field descriptions:

Donor
The individual or organization that gives a donation. Each donor has a person record (name, contact details) and an address.
Finder
A short, unique code printed on a mailing piece (e.g. an envelope or reply card) that lets us match an incoming donation back to the specific donor and campaign that prompted it.
Mail code
A campaign code that identifies which mailing the donation responded to. Used for attribution and reporting.
White mail
A donation that arrived without a finder code attached — for example, a cheque sent in a plain envelope. These are tagged with the mail code "WM" by default.
Effective mail date
The date the donation should be reported against. Usually the date the mailing was sent; in some cases it is adjusted by an internal back-office process so that donations from related mailings group together.
Caging
The back-office process of opening, recording, and depositing donations. The /donations/caging endpoint returns the same data points used in our nightly reconciliation export.
Old_* fields
When donor or address details are corrected on the day a donation is recorded, the previous values are captured in the activity log. The Old_* fields expose those prior values, scoped to changes made on the same calendar day as the donation. They will be empty when no correction was made.
Token scope
A permission attached to your access token. We issue two scopes: read-donations (financial fields) and read-pii (donor personal information). A token can hold one or both.

Errors

The API uses standard HTTP status codes. The response body is always JSON; on errors it includes a message and (where applicable) a list of the specific fields that failed.

StatusMeaning
401Missing, invalid, or expired bearer token. Re-authenticate at /oauth/token.
403Your token does not hold a scope required by an explicitly opted-in field. The response lists forbidden_fields.
404The donation ID does not exist.
422Invalid input — for example, an unknown field name in optional_fields, a date window longer than 31 days, or a malformed date. The response body details what is wrong.
429Rate limit exceeded. Wait the number of seconds in the Retry-After header before retrying.
503The API has been temporarily disabled by an administrator. Contact your MailForce administrator.
5xx (other)Something went wrong on our side. Please retry with backoff; if it persists, contact your MailForce administrator.