Operation Charity API

Getting started

Connect your website, forms or spreadsheets to Operation Charity in a few minutes. Everything the app does, the API can do.

1. Create an API key

An admin signs in and opens Settings → API keys, names the key after what will use it ("Website donation form") and picks its access:

  • Read can look things up.
  • Read and write can also add and change records.

The key is shown once. Store it where your server keeps secrets, never in a web page or a public repository. If it leaks, revoke it and create another; revoking takes effect at once. Keys cannot manage users, other keys or webhooks.

2. Make your first request

Send the key as a bearer token. This asks who the key belongs to:

curl: who am I
curl https://api.operationcharity.org/v1/users/me \
  -H "Authorization: Bearer $OC_API_KEY"

Then list your contacts:

curl: list contacts
curl "https://api.operationcharity.org/v1/contacts?limit=20" \
  -H "Authorization: Bearer $OC_API_KEY"

Add one (needs a read and write key):

curl: create a contact
curl https://api.operationcharity.org/v1/contacts \
  -H "Authorization: Bearer $OC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind": "person", "first_name": "Aiko", "last_name": "Nakamura", "email": "aiko@example.com"}'

Settings → API keys shows when each key was last used, so you can see the request arrived.

3. Page through lists

Lists return { "data": [...], "next_cursor": "..." }. Pass next_cursor back as cursor until it is null. limit is 1–200 (50 by default). Cursors expire after 24 hours.

JavaScript: read every contact
let cursor = null;
do {
  const url = new URL("https://api.operationcharity.org/v1/contacts");
  url.searchParams.set("limit", "200");
  if (cursor) url.searchParams.set("cursor", cursor);
  const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.OC_API_KEY}` } });
  const page = await res.json();
  for (const contact of page.data) console.log(contact.display_name);
  cursor = page.next_cursor;
} while (cursor);

4. Record money safely with idempotency keys

Creating gifts, gift adjustments, receipts, deposit batches and import commits needs an Idempotency-Key header: any unique string, such as a UUID you generate once per gift. If the network fails and you send the same request again with the same key, you get the first answer back instead of a second gift. The same key with a different body is refused with 409. Keys are kept for 24 hours.

curl: record a gift
curl https://api.operationcharity.org/v1/gifts \
  -H "Authorization: Bearer $OC_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 5d0e8f7a-2b1c-4c3d-9e8f-7a6b5c4d3e2f" \
  -d '{"contact_id": "<contact id>", "fund_id": "<fund id>", "amount_cents": 5000, "received_on": "2026-10-05", "payment_method": "card"}'

Amounts are whole cents (5000 is $50.00). Gifts are never edited; corrections are gift adjustments.

5. Handle errors and limits

Errors are JSON problem details with a plain-language detail; validation errors list each field in errors. Each key can make 600 requests a minute. Every response says how many are left in RateLimit-Remaining; over the limit you get 429 with Retry-After in seconds.

Example error
{
  "type": "https://docs.operationcharity.org/problems/validation",
  "title": "Validation failed",
  "status": 422,
  "detail": "One or more fields are invalid.",
  "errors": [{ "pointer": "/amount_cents", "message": "must be an integer greater than 0" }]
}

Next

  • API reference: every operation with its fields and examples.
  • Webhooks: get told when records change instead of polling.