Transactions

Transactions are financial records linked to contacts. The system maintains aggregate records at both the contact level and org level, updated atomically on create and delete.

All endpoints require authentication via the X-API-Key header. See Authentication.


Create a transaction

POST /transactions

Uses a MongoDB transaction to atomically update contact-level and org-level aggregate records.

Request body

FieldTypeRequiredNotes
contactIdstringYesContact ID.
namestringYesTransaction name/description.
amountnumberYesTransaction amount.
currencystringYesOnly usd is supported.
typestringYesTransaction type (lowercased).
providerstringNostakd (default) or stripe.
providerIdstringNoExternal provider reference ID.
timestampnumberYesUnix timestamp of the transaction.

Example request

curl -X POST https://<host>/api/transactions \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "contactId": "675a...",
    "name": "Season Ticket Renewal",
    "amount": 2500,
    "currency": "usd",
    "type": "renewal",
    "timestamp": 1716000000000
  }'

Response

{
  "success": true,
  "data": {
    "_id": "675a...",
    "contactId": "675a...",
    "name": "Season Ticket Renewal",
    "amount": 2500,
    "currency": "usd",
    "type": "renewal",
    "provider": "stakd",
    "providerId": null,
    "timestamp": 1716000000000,
    "createdAt": 1716000000000,
    "updatedAt": 1716000000000
  }
}

Side effects

On create, the system atomically updates:

  • Contact aggregate (transactions_record): sets lastTransactionId, lastTransactionAmount, lastTransactionTimestamp to the new transaction and increments totalTransactionsAmount.
  • Org aggregate (transactions_record_org): increments totalTransactionsAmount.

Errors

StatusMessageCause
400Validation errorMissing required fields or invalid currency.

List transactions

GET /transactions

Query parameters

ParameterTypeDefaultNotes
contactIdstring—Filter by contact ID.

Example request

curl "https://<host>/api/transactions?contactId=675a..." \
  -H "X-API-Key: your-api-key"

Response

{
  "success": true,
  "data": [
    {
      "_id": "675a...",
      "contactId": "675a...",
      "name": "Season Ticket Renewal",
      "amount": 2500,
      "currency": "usd",
      "type": "renewal",
      "provider": "stakd",
      "timestamp": 1716000000000,
      "createdAt": 1716000000000
    }
  ]
}

Delete a transaction

DELETE /transactions

Uses a MongoDB transaction to atomically update contact-level and org-level aggregate records.

Query parameters

ParameterTypeRequiredNotes
idstringYesTransaction ID.

Example request

curl -X DELETE "https://<host>/api/transactions?id=675a..." \
  -H "X-API-Key: your-api-key"

Response

{
  "success": true,
  "message": "Success"
}

Side effects

On delete, the system atomically:

  • Updates the contact aggregate's lastTransaction* fields to the previous transaction (or null if none remain).
  • Decrements totalTransactionsAmount in both contact and org aggregates.

Errors

StatusMessageCause
404Not foundTransaction does not exist.

List transaction types

GET /transactions/types

Returns all distinct transaction type strings used in the org.

Example request

curl "https://<host>/api/transactions/types" \
  -H "X-API-Key: your-api-key"

Response

{
  "success": true,
  "data": ["donation", "renewal", "merchandise"]
}

Response envelope

All endpoints use the standard envelope:

{
  "error": false,
  "success": true,
  "message": "...",
  "code": 200,
  "data": "...",
  "pagination": {}
}

Error responses:

{
  "error": true,
  "success": false,
  "message": "...",
  "code": 400,
  "data": null
}

data is null for a general failure, such as a database error or an upstream timeout. When the request is rejected because of a specific field, data carries the field(s) at fault instead:

{
  "error": true,
  "success": false,
  "message": "phone: Phone parse failed: The string supplied did not seem to be a phone number (input: <masked>)",
  "code": 400,
  "data": {
    "issues": [
      {
        "path": "phone",
        "message": "Phone parse failed: The string supplied did not seem to be a phone number (input: <masked>)"
      }
    ]
  }
}

Each issue carries a path (the dotted path into the request body) and a message. A label is added when the path is not self-describing, which today means custom fields only, for example "path": "fields.68b0a1f39c2e4d0012ab77e5", "label": "Class Year". issues is an array, but treat it as holding at most one entry: validation stops at the first failure.

On this page