Keep it yours

Agent keys and commands

Every endpoint and command an agent or script can call, and who may call it.

Everything the screens do goes through the same server handlers, which check the caller's role on every request. Use the app's real origin.

Who can do what

Actor Allowed
Owner Everything below, plus backup, restore (/api/portability/*) and donor.erase.
Admin All commands except backup, restore and erasure; CSV downloads; the Stripe move-in.
Member Read; donor.create, donor.update, gift.record, pledge.create, consent.record.
Agent key Read; ack.prepare and appeal.prepare only.

An agent key is created in Data & access (or {"command":"agent.create","input":{"label":"…"}} by an owner or admin). The response holds {id,label,token,expiresAt} once; expiresAt is epoch milliseconds, 30 days ahead. Revoke with agent.revoke and {"id":"<key-id>"}. A key stops working when it is revoked, expires, or the person who created it is deactivated or changes role. A key can read donor records: treat it as private data access.

Agents send Authorization: Bearer <token>. Native sessions send their cookie and, for changes, the exact Origin. Never send an actor or role field; the server decides who you are.

Reading

Request Returns
GET /api/coffer/donors?q=&page= {donors,more,page,q,total}, 50 per page; q matches name, email or household.
GET /api/coffer/donors/<id> {donor,household,gifts,pledges,consents,letters,appeals,events,totals,current}.
GET /api/coffer/letters {today,thresholdCents,targetDays,due,batches}; due gifts carry due_on, overdue and goods.
GET /api/coffer/letters/<batch-id> {batch,letters,approval,events}.
GET /api/coffer/appeals and /api/coffer/appeals/<id> Appeals; one appeal with recipients, approval and consentChanged.
GET /api/coffer/settings Letter settings, including version and reviewed_at.
GET /api/coffer/letters/<id>/letters.csv, /api/coffer/appeals/<id>/recipients.csv Exported files (owner or admin, after export).

Changing

HTTP
POST /api/coffer/commands
Content-Type: application/json
Authorization: Bearer <token>

{"command":"ack.prepare","input":{}}

Errors return {error,message,details?} with a 4xx status. Refresh and review before repeating a refused decision. Money is either typed dollars ("amount":"250.00") or exact integer cents ("amountCents":25000); a bare number in amount is refused.

Command Input Notes
donor.create name, optional email, phone, household, addressLine1, addressLine2, city, region, postalCode, country Email is unique.
donor.update donorId, version, the same fields Version must match.
gift.record donorId, amount/amountCents, receivedOn (YYYY-MM-DD, not future), method (check, card, online, cash, bank transfer, other), goods (none or provided), with goodsValue and goodsDescription when provided; optional reference, fund, pledgeId, note Reference is unique among hand-entered gifts.
gift.reverse giftId, reason, optional reversedOn Adds the negative line; once per gift; refused while the gift is in a prepared or approved letter batch.
gift.classify giftId, version, goods, goods fields Only for an imported gift whose answer is unknown.
pledge.create donorId, amount, pledgedOn, optional dueBy, fund
consent.record donorIds (1–500), channel (email, mail), status (granted, withdrawn), method (web form, paper form, verbal, email reply, event sign-up, other), evidence, capturedOn Appends; never edits.
settings.update version, organisation, ein, timezone, threshold, targetDays, letterSubject, letterTemplate, noneStatement, goodsStatement See the placeholders on the settings page.
ack.prepare optional giftIds Agents may run it. Needs reviewed settings; at most 500 letters.
ack.approve, ack.discard, ack.export batchId, version Approval binds the content hash; export repeats return the same record.
ack.markSent batchId, version, sentOn, method (mail, email, mail and email)
appeal.prepare name, channel, optional givenSince, minTotal Agents may run it. Returns recipients and excluded counts.
appeal.approve, appeal.discard, appeal.export appealId, version Refused with CONSENT_CHANGED if any recipient's consent moved.
appeal.markSent appealId, version, sentOn
donor.erase donorId, confirm: "ERASE" Owner only. Returns the anonymous placeholder's donorId.

Stripe move-in

Owner or admin. Bodies up to 12 MiB of JSON.

Request Body
POST /api/coffer/imports/preview {customers:[page…], charges:[page…], noGoodsOrServices}
POST /api/coffer/imports/apply The same plus fingerprint from the preview. Repeating it continues the same job.
POST /api/coffer/imports/resume, /undo {id}
GET /api/coffer/imports/journal?id=<job-id> Job, plan and retained originals.

Backup and restore

The owner-only Base portability protocol is mounted at /api/portability/* (exports, exports/<id>/advance, exports/<id>/download, imports, imports/<id>/parts/<n>, imports/<id>/finalize). The command line (rer export, rer import) is the simpler route; see the README.