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 https://api.operationcharity.org/v1/users/me \
-H "Authorization: Bearer $OC_API_KEY"Then list your 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 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.
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 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.
{
"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.