Integration reference

Customer accounts API

Manage system accounts, read unpaid balances, and connect customers to accounts. Every endpoint below includes its request contract and complete response example.

Before you start

Follow Get started: create an API key to generate your first credential and make a test request.

Use https://bottlespeed.com and the target system’s slug. Send one system-key header consistently:

x-api-key: <system-api-key>

# Alternative:
Authorization: Bearer <system-api-key>

x-api-key takes precedence. Read keys permit reads; Read-Write keys permit reads and writes. The creator must remain an active system Owner or PrimaryOwner for account management. Search also supports active Employee/Manager keys. Application sessions continue to work for the app; third-party integrations use system API keys.

Account balances are signed dollars, not cents. 42.5 means $42.50. Balances can be negative. Read unpaidBalance directly; lifetime return totals can be attributed to a different returning account and do not reconstruct the balance.

Examples use fictional IDs and customer data. Path placeholders must be replaced with actual IDs. JSON shown under Request is the HTTP body; GET and body-free mutations explicitly say so. Timestamps are ISO 8601 strings.

Save the account id, global userId, system membershipId, and assignment accountUserId separately. Change or remove an assignment using accountUserId.

List accounts

GET/api/system/{systemSlug}/customer-account

Read all accounts in one system, including unpaid balances and assigned users. Active accounts sort first, followed by account name and ID.

Authentication: System API key with Read or Read-Write permission, owned by an active system Owner or PrimaryOwner.

Request

GET /api/system/{systemSlug}/customer-account?page=1&pageSize=25
x-api-key: <system-api-key>

Path parameters

FieldTypeDetails
systemSlugstring · requiredSystem slug from /application/{systemSlug}/profile.

Query parameters

FieldTypeDetails
pageinteger · optional1–90,071,992,547,409; defaults to 1 when pageSize is supplied.
pageSizeinteger · optional1–100; defaults to 25 when page is supplied.

Request body: none.

Response · 200 OK

{
  "customerAccounts": [
    {
      "id": "11111111-1111-4111-8111-111111111111",
      "name": "Community Returns",
      "address": null,
      "payee": "Community Returns",
      "hasAchAccountNumber": false,
      "hasAchRoutingNumber": false,
      "active": true,
      "unpaidBalance": 42.5,
      "totalReturnedContainers": 1000,
      "totalReturnedValue": 100,
      "userCount": 1,
      "users": [
        {
          "accountUserId": "22222222-2222-4222-8222-222222222222",
          "customerAccountId": "11111111-1111-4111-8111-111111111111",
          "membershipId": "33333333-3333-4333-8333-333333333333",
          "userId": "44444444-4444-4444-8444-444444444444",
          "email": "alex@example.com",
          "active": true,
          "firstName": "Alex",
          "lastName": "River",
          "fullName": "Alex River",
          "cellphone": "(202)-555-0123",
          "licensePlateNumber": null,
          "driversLicenseNumber": null,
          "activeIdDocument": null,
          "customerAccountRole": "Owner"
        }
      ]
    }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 25,
    "totalItems": 1,
    "totalPages": 1,
    "hasPreviousPage": false,
    "hasNextPage": false
  }
}
FieldTypeDetails
customerAccountsCustomerAccount[]Current page, including active and inactive accounts.
paginationPagination · conditionalPresent only when page or pageSize is supplied.

Omit both query parameters for the unpaginated response containing only customerAccounts. No search/status filters are supported here. An empty system returns [] and totalPages: 0. A page past the end stays at the requested page and returns [].

Increment page until hasNextPage is false. totalItems counts accounts, not users. Pages are live offset-based reads; reconcile by account id if records change between requests.

See response object fields and error responses.

Search accounts

GET/api/system/{systemSlug}/customer-account/search

Search account names or assigned users’ email/phone. Results are unpaginated.

Authentication: System API key with Read or Read-Write permission, owned by an active system Owner or PrimaryOwner. Employee and Manager system keys can also search; their responses omit ACH saved-state flags.

Request

GET /api/system/{systemSlug}/customer-account/search?nameQuery=Community&status=both
x-api-key: <system-api-key>

Path parameters

FieldTypeDetails
systemSlugstring · requiredSystem slug from /application/{systemSlug}/profile.

Query parameters

FieldTypeDetails
nameQuerystring · requiredTrimmed, 1–255 characters. URL-encode the search text.
statusenum · optionalactive, inactive, or both (default).

Request body: none.

Response · 200 OK

{
  "customerAccounts": [
    {
      "id": "11111111-1111-4111-8111-111111111111",
      "name": "Community Returns",
      "address": null,
      "payee": "Community Returns",
      "hasAchAccountNumber": false,
      "hasAchRoutingNumber": false,
      "active": true,
      "unpaidBalance": 42.5,
      "totalReturnedContainers": 1000,
      "totalReturnedValue": 100,
      "userCount": 1,
      "users": [
        {
          "accountUserId": "22222222-2222-4222-8222-222222222222",
          "customerAccountId": "11111111-1111-4111-8111-111111111111",
          "membershipId": "33333333-3333-4333-8333-333333333333",
          "userId": "44444444-4444-4444-8444-444444444444",
          "email": "alex@example.com",
          "active": true,
          "firstName": "Alex",
          "lastName": "River",
          "fullName": "Alex River",
          "cellphone": "(202)-555-0123",
          "licensePlateNumber": null,
          "driversLicenseNumber": null,
          "activeIdDocument": null,
          "customerAccountRole": "Owner"
        }
      ]
    }
  ]
}
FieldTypeDetails
customerAccountsCustomerAccount[]Matching accounts; [] when none match. No pagination object.

See response object fields and error responses.

Create an account

POST/api/system/{systemSlug}/customer-account

Create an active account with zero balance and lifetime totals. This does not create a customer, assign an owner, or send an invitation.

Authentication: System API key with Read-Write permission, owned by an active system Owner or PrimaryOwner.

Request

POST /api/system/{systemSlug}/customer-account
x-api-key: <system-api-key>
Content-Type: application/json

Path parameters

FieldTypeDetails
systemSlugstring · requiredSystem slug from /application/{systemSlug}/profile.
FieldTypeDetails
namestring · requiredTrimmed, 1–255 characters. Exact duplicate names in this system, including inactive accounts, are rejected.
addressstring | null · requiredBlank strings normalize to null.
payeestring | null · requiredBlank strings normalize to null.
achAccountNumberstring | null · optional1–17 digits; use a string to preserve leading zeroes. Blank becomes null. Encrypted before storage.
achRoutingNumberstring | null · optionalExactly 9 digits. Blank becomes null. Encrypted before storage.
{
  "name": "Community Returns",
  "address": null,
  "payee": "Community Returns",
  "achAccountNumber": null,
  "achRoutingNumber": null
}

Add account users separately. Omitted or blank ACH values create no saved bank value.

Response · 200 OK

{
  "customerAccount": {
    "id": "11111111-1111-4111-8111-111111111111",
    "name": "Community Returns",
    "address": null,
    "payee": "Community Returns",
    "hasAchAccountNumber": false,
    "hasAchRoutingNumber": false,
    "active": true,
    "unpaidBalance": 0,
    "totalReturnedContainers": 0,
    "totalReturnedValue": 0,
    "userCount": 0,
    "users": []
  }
}
FieldTypeDetails
customerAccountCustomerAccount | nullComplete account object; see the field reference below. A concurrent deletion can make this null.

See response object fields and error responses.

Update or reactivate an account

PUT/api/system/{systemSlug}/customer-account/{accountId}

Update account details or reactivate an inactive account. Required name/address/payee fields must be sent even when changing only active status.

Authentication: System API key with Read-Write permission, owned by an active system Owner or PrimaryOwner.

Request

PUT /api/system/{systemSlug}/customer-account/{accountId}
x-api-key: <system-api-key>
Content-Type: application/json

Path parameters

FieldTypeDetails
systemSlugstring · requiredSystem slug from /application/{systemSlug}/profile.
accountIdUUID · requiredAccount id from the collection or search response. Must belong to this system.
FieldTypeDetails
namestring · requiredTrimmed, 1–255 characters. Exact duplicate names in this system, including inactive accounts, are rejected.
addressstring | null · requiredBlank strings normalize to null.
payeestring | null · requiredBlank strings normalize to null.
achAccountNumberstring | null · optional1–17 digits; use a string to preserve leading zeroes. Blank becomes null. Encrypted before storage.
achRoutingNumberstring | null · optionalExactly 9 digits. Blank becomes null. Encrypted before storage.
activeboolean · optionalOmit to preserve the current state; true reactivates the account.
removeAchAccountNumberboolean · optionaltrue explicitly removes the saved account number.
removeAchRoutingNumberboolean · optionaltrue explicitly removes the saved routing number.
{
  "name": "Community Returns",
  "address": null,
  "payee": "Community Returns",
  "active": true,
  "achAccountNumber": null,
  "achRoutingNumber": null,
  "removeAchAccountNumber": false,
  "removeAchRoutingNumber": false
}

Omitted, null, or blank ACH replacements preserve saved values. A nonblank replacement and its removal flag cannot be sent together. The same name is allowed for this account; another account with that name is rejected.

Response · 200 OK

{
  "customerAccount": {
    "id": "11111111-1111-4111-8111-111111111111",
    "name": "Community Returns",
    "address": null,
    "payee": "Community Returns",
    "hasAchAccountNumber": false,
    "hasAchRoutingNumber": false,
    "active": true,
    "unpaidBalance": 42.5,
    "totalReturnedContainers": 1000,
    "totalReturnedValue": 100,
    "userCount": 1,
    "users": [
      {
        "accountUserId": "22222222-2222-4222-8222-222222222222",
        "customerAccountId": "11111111-1111-4111-8111-111111111111",
        "membershipId": "33333333-3333-4333-8333-333333333333",
        "userId": "44444444-4444-4444-8444-444444444444",
        "email": "alex@example.com",
        "active": true,
        "firstName": "Alex",
        "lastName": "River",
        "fullName": "Alex River",
        "cellphone": "(202)-555-0123",
        "licensePlateNumber": null,
        "driversLicenseNumber": null,
        "activeIdDocument": null,
        "customerAccountRole": "Owner"
      }
    ]
  }
}
FieldTypeDetails
customerAccountCustomerAccount | nullComplete account object; see the field reference below. A concurrent deletion can make this null.

See response object fields and error responses.

Deactivate an account

POST/api/system/{systemSlug}/customer-account/{accountId}/deactivate

Mark the account inactive while preserving its users, balance, and history. Reactivate with the update endpoint.

Authentication: System API key with Read-Write permission, owned by an active system Owner or PrimaryOwner.

Request

POST /api/system/{systemSlug}/customer-account/{accountId}/deactivate
x-api-key: <system-api-key>

Path parameters

FieldTypeDetails
systemSlugstring · requiredSystem slug from /application/{systemSlug}/profile.
accountIdUUID · requiredAccount id from the collection or search response. Must belong to this system.

Request body: none.

Response · 200 OK

{
  "customerAccount": {
    "id": "11111111-1111-4111-8111-111111111111",
    "name": "Community Returns",
    "address": null,
    "payee": "Community Returns",
    "hasAchAccountNumber": false,
    "hasAchRoutingNumber": false,
    "active": false,
    "unpaidBalance": 42.5,
    "totalReturnedContainers": 1000,
    "totalReturnedValue": 100,
    "userCount": 1,
    "users": [
      {
        "accountUserId": "22222222-2222-4222-8222-222222222222",
        "customerAccountId": "11111111-1111-4111-8111-111111111111",
        "membershipId": "33333333-3333-4333-8333-333333333333",
        "userId": "44444444-4444-4444-8444-444444444444",
        "email": "alex@example.com",
        "active": true,
        "firstName": "Alex",
        "lastName": "River",
        "fullName": "Alex River",
        "cellphone": "(202)-555-0123",
        "licensePlateNumber": null,
        "driversLicenseNumber": null,
        "activeIdDocument": null,
        "customerAccountRole": "Owner"
      }
    ]
  }
}
FieldTypeDetails
customerAccountCustomerAccount | nullComplete account object; see the field reference below. A concurrent deletion can make this null.

See response object fields and error responses.

Create or reactivate a customer

POST/api/system/{systemSlug}/customers

Provision a system Customer membership before assigning that customer to an account. This may create a global user or reactivate an existing Customer membership.

Authentication: System API key with Read-Write permission, owned by an active system Owner or PrimaryOwner.

Request

POST /api/system/{systemSlug}/customers
x-api-key: <system-api-key>
Content-Type: application/json

Path parameters

FieldTypeDetails
systemSlugstring · requiredSystem slug from /application/{systemSlug}/profile.
FieldTypeDetails
emailstring | null · optionalValid email, trimmed and lowercased; blank becomes null. At least one of email/cellphone must be nonblank.
cellphonestring | null · optionalUS ten-digit or +1 phone, normalized to E.164. Blank becomes null. Every supplied contact must belong to the same global user.
firstNamestring · requiredTrimmed, 1–255 characters.
lastNamestring · requiredTrimmed, 1–255 characters.
notesstring | null · requiredBlank becomes null.
licensePlateNumberstring | null · optionalTrimmed, up to 255 characters. Stored only when Connecticut reporting is enabled.
driversLicenseNumberstring | null · optionalTrimmed, up to 255 characters. Stored only when Connecticut reporting is enabled.
{
  "email": "alex@example.com",
  "cellphone": "+12025550123",
  "firstName": "Alex",
  "lastName": "River",
  "notes": null,
  "licensePlateNumber": null,
  "driversLicenseNumber": null
}

Omit the storeId query parameter for this owner integration. The POS store-scoped path has different authorization and a reduced response.

This writes names, notes, and applicable identity fields on existing Customer memberships. Existing global contact ownership must be respected; staff cannot be converted. Managed updates cannot replace verified login contacts. This call does not verify contacts, send an OTP, or add an account assignment.

Response · 200 OK

{
  "customerMember": {
    "membershipId": "33333333-3333-4333-8333-333333333333",
    "userId": "44444444-4444-4444-8444-444444444444",
    "email": "alex@example.com",
    "active": true,
    "firstName": "Alex",
    "lastName": "River",
    "fullName": "Alex River",
    "cellphone": "(202)-555-0123",
    "role": "Customer",
    "hourlyRate": 0,
    "posPinCode": null,
    "notes": null,
    "licensePlateNumber": null,
    "driversLicenseNumber": null,
    "activeIdDocument": null
  }
}
FieldTypeDetails
customerMemberCustomerMember | nullSystem membership with global user/contact fields; use the saved contact in the account-user endpoint.

See response object fields and error responses.

Add an account user

POST/api/system/{systemSlug}/customer-account/{accountId}/users

Assign an existing active Customer in this system to the account. A customer can have multiple account assignments.

Authentication: System API key with Read-Write permission, owned by an active system Owner or PrimaryOwner.

Request

POST /api/system/{systemSlug}/customer-account/{accountId}/users
x-api-key: <system-api-key>
Content-Type: application/json

Path parameters

FieldTypeDetails
systemSlugstring · requiredSystem slug from /application/{systemSlug}/profile.
accountIdUUID · requiredAccount id from the collection or search response. Must belong to this system.
FieldTypeDetails
emailstring | null · optionalValid email, trimmed and lowercased; blank becomes null. At least one of email/cellphone must be nonblank.
cellphonestring | null · optionalUS ten-digit or +1 phone, normalized to E.164. Blank becomes null. Every supplied contact must belong to the same global user.
customerAccountRoleenum · requiredOwner, Admin, or User. This account role grants no system-wide API access.
{
  "email": "alex@example.com",
  "cellphone": "+12025550123",
  "customerAccountRole": "Owner"
}

Email-only and phone-only requests are supported. Every supplied contact must match the same saved user. Missing/inactive customers, staff memberships, conflicting contacts, and duplicate assignments return 400. No login, OTP, or invitation is created.

Response · 200 OK

{
  "accountUser": {
    "accountUserId": "22222222-2222-4222-8222-222222222222",
    "customerAccountId": "11111111-1111-4111-8111-111111111111",
    "membershipId": "33333333-3333-4333-8333-333333333333",
    "userId": "44444444-4444-4444-8444-444444444444",
    "email": "alex@example.com",
    "active": true,
    "firstName": "Alex",
    "lastName": "River",
    "fullName": "Alex River",
    "cellphone": "(202)-555-0123",
    "licensePlateNumber": null,
    "driversLicenseNumber": null,
    "activeIdDocument": null,
    "customerAccountRole": "Owner"
  }
}
FieldTypeDetails
accountUserAccountUser | nullCreated or updated assignment with its system membership and global contact details. A concurrent deletion can make this null.

See response object fields and error responses.

Change an account user’s role

PUT/api/system/{systemSlug}/customer-account/{accountId}/users/{accountUserId}

Change the role on one account assignment. Demoting the only remaining Owner is rejected.

Authentication: System API key with Read-Write permission, owned by an active system Owner or PrimaryOwner.

Request

PUT /api/system/{systemSlug}/customer-account/{accountId}/users/{accountUserId}
x-api-key: <system-api-key>
Content-Type: application/json

Path parameters

FieldTypeDetails
systemSlugstring · requiredSystem slug from /application/{systemSlug}/profile.
accountIdUUID · requiredAccount id from the collection or search response. Must belong to this system.
accountUserIdUUID · requiredAssignment id from users[].accountUserId; not the global userId or membershipId.
FieldTypeDetails
customerAccountRoleenum · requiredOwner, Admin, or User.
{
  "customerAccountRole": "Admin"
}

This example assumes another Owner remains. The assignment must belong to this account and system.

Response · 200 OK

{
  "accountUser": {
    "accountUserId": "22222222-2222-4222-8222-222222222222",
    "customerAccountId": "11111111-1111-4111-8111-111111111111",
    "membershipId": "33333333-3333-4333-8333-333333333333",
    "userId": "44444444-4444-4444-8444-444444444444",
    "email": "alex@example.com",
    "active": true,
    "firstName": "Alex",
    "lastName": "River",
    "fullName": "Alex River",
    "cellphone": "(202)-555-0123",
    "licensePlateNumber": null,
    "driversLicenseNumber": null,
    "activeIdDocument": null,
    "customerAccountRole": "Admin"
  }
}
FieldTypeDetails
accountUserAccountUser | nullCreated or updated assignment with its system membership and global contact details. A concurrent deletion can make this null.

See response object fields and error responses.

Remove an account user

DELETE/api/system/{systemSlug}/customer-account/{accountId}/users/{accountUserId}

Remove only the account assignment. Preserve the global user, system membership, other assignments, and account history.

Authentication: System API key with Read-Write permission, owned by an active system Owner or PrimaryOwner.

Request

DELETE /api/system/{systemSlug}/customer-account/{accountId}/users/{accountUserId}
x-api-key: <system-api-key>

Path parameters

FieldTypeDetails
systemSlugstring · requiredSystem slug from /application/{systemSlug}/profile.
accountIdUUID · requiredAccount id from the collection or search response. Must belong to this system.
accountUserIdUUID · requiredAssignment id from users[].accountUserId; not the global userId or membershipId.

Request body: none.

Removal currently permits removing the last account Owner. Deleting the same assignment again returns 400.

Response · 200 OK

{
  "success": true,
  "accountUserId": "22222222-2222-4222-8222-222222222222"
}
FieldTypeDetails
successbooleantrue after deletion.
accountUserIdUUIDThe deleted assignment id.

See response object fields and error responses.

List account transactions

GET/api/system/{systemSlug}/customer-account/{accountId}/transactions

Read transactions where this account was paid or recorded as the returning account. History can include activity that did not change this account’s balance.

Authentication: System API key with Read or Read-Write permission, owned by an active system Owner or PrimaryOwner.

Request

GET /api/system/{systemSlug}/customer-account/{accountId}/transactions?page=1&pageSize=25
x-api-key: <system-api-key>

Path parameters

FieldTypeDetails
systemSlugstring · requiredSystem slug from /application/{systemSlug}/profile.
accountIdUUID · requiredAccount id from the collection or search response. Must belong to this system.

Query parameters

FieldTypeDetails
pageinteger · optionalAt least 1; default 1. A page past the end is clamped to the last page, or 1 when empty.
pageSizeinteger · optional1–100; default 25.

Request body: none.

Response · 200 OK

{
  "transactions": [
    {
      "id": "66666666-6666-4666-8666-666666666666",
      "createdAt": "2026-09-15T12:00:00.000Z",
      "storeName": "Main store",
      "grandTotal": -10,
      "paidSubTotal": -10,
      "salesSubTotal": 0,
      "taxesSubTotal": 0,
      "containerDepositsSubTotal": 0,
      "roundingAdjustmentTotal": 0,
      "totalReturnedContainers": 100,
      "paymentMethod": "Customer Account",
      "isPaidToCustomerAccount": true,
      "paidToCustomerAccountId": "11111111-1111-4111-8111-111111111111",
      "paidToCustomerAccountName": "Community Returns",
      "returnedByCustomerAccountId": "11111111-1111-4111-8111-111111111111",
      "returnedByCustomerAccountName": "Community Returns",
      "fundraiserId": null,
      "fundraiserName": null,
      "isTransactionRefunded": false,
      "isPaidToLinkedAccount": true,
      "isReturnedByLinkedAccount": true
    }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 25,
    "totalItems": 1,
    "totalPages": 1,
    "hasPreviousPage": false,
    "hasNextPage": false
  }
}
FieldTypeDetails
transactionsAccountTransaction[]Newest first by createdAt, then id.
paginationPaginationAlways included; totalPages is 0 when empty.

See response object fields and error responses.

List account payouts

GET/api/system/{systemSlug}/customer-account/{accountId}/payouts

Read payout and correction adjustments, newest first. This endpoint is unpaginated.

Authentication: System API key with Read or Read-Write permission, owned by an active system Owner or PrimaryOwner.

Request

GET /api/system/{systemSlug}/customer-account/{accountId}/payouts
x-api-key: <system-api-key>

Path parameters

FieldTypeDetails
systemSlugstring · requiredSystem slug from /application/{systemSlug}/profile.
accountIdUUID · requiredAccount id from the collection or search response. Must belong to this system.

Request body: none.

Response · 200 OK

{
  "payouts": [
    {
      "id": "55555555-5555-4555-8555-555555555555",
      "customerAccountId": "11111111-1111-4111-8111-111111111111",
      "adjustmentType": "Payout",
      "totalPayoutAmount": 12.5,
      "payoutMethod": "Check",
      "checkNumber": "1042",
      "notes": null,
      "createdAt": "2026-09-15T12:00:00.000Z",
      "updatedAt": "2026-09-15T12:00:00.000Z"
    }
  ]
}
FieldTypeDetails
payoutsAccountPayout[]All adjustments for this account; [] when none exist. adjustmentType distinguishes Payout and Correction.

See response object fields and error responses.

Record an account payout

POST/api/system/{systemSlug}/customer-account/{accountId}/payouts

Record a cash or check payout and reduce the active account’s unpaid balance. This records the payment; it does not send ACH or issue a check.

Authentication: System API key with Read-Write permission, owned by an active system Owner or PrimaryOwner.

Request

POST /api/system/{systemSlug}/customer-account/{accountId}/payouts
x-api-key: <system-api-key>
Content-Type: application/json

Path parameters

FieldTypeDetails
systemSlugstring · requiredSystem slug from /application/{systemSlug}/profile.
accountIdUUID · requiredAccount id from the collection or search response. Must belong to this system.
FieldTypeDetails
totalPayoutAmountnumber · requiredDollar amount greater than 0, at most 1 billion, and no greater than the current unpaid balance.
payoutMethodenum · requiredCheck or Cash.
checkNumberstring | null · requiredNonblank for Check; null/blank for Cash.
notesstring | null · requiredBlank becomes null.
{
  "totalPayoutAmount": 12.5,
  "payoutMethod": "Check",
  "checkNumber": "1042",
  "notes": null
}

The balance check and deduction use a database transaction. No idempotency-key contract exists: after an uncertain response, reconcile payout history before retrying. Account and assignment creation also require reconciliation before retrying an uncertain write.

Response · 200 OK

{
  "payout": {
    "id": "55555555-5555-4555-8555-555555555555",
    "customerAccountId": "11111111-1111-4111-8111-111111111111",
    "adjustmentType": "Payout",
    "totalPayoutAmount": 12.5,
    "payoutMethod": "Check",
    "checkNumber": "1042",
    "notes": null,
    "createdAt": "2026-09-15T12:00:00.000Z",
    "updatedAt": "2026-09-15T12:00:00.000Z"
  },
  "customerAccount": {
    "id": "11111111-1111-4111-8111-111111111111",
    "name": "Community Returns",
    "address": null,
    "payee": "Community Returns",
    "active": true,
    "unpaidBalance": 30,
    "totalReturnedContainers": 1000,
    "totalReturnedValue": 100,
    "userCount": 1,
    "users": [
      {
        "accountUserId": "22222222-2222-4222-8222-222222222222",
        "customerAccountId": "11111111-1111-4111-8111-111111111111",
        "membershipId": "33333333-3333-4333-8333-333333333333",
        "userId": "44444444-4444-4444-8444-444444444444",
        "email": "alex@example.com",
        "active": true,
        "firstName": "Alex",
        "lastName": "River",
        "fullName": "Alex River",
        "cellphone": "(202)-555-0123",
        "licensePlateNumber": null,
        "driversLicenseNumber": null,
        "activeIdDocument": null,
        "customerAccountRole": "Owner"
      }
    ]
  }
}
FieldTypeDetails
payoutAccountPayout | nullThe recorded adjustment.
customerAccountCustomerAccount | nullUpdated account. This endpoint omits hasAchAccountNumber and hasAchRoutingNumber.

See response object fields and error responses.

Read API audit history

GET/api/system/{systemSlug}/api-audit-logs

Read this system’s recorded API activity. Owners can also browse /application/{systemSlug}/api-audit-logs.

Authentication: System API key with Read or Read-Write permission, owned by an active system Owner or PrimaryOwner.

Request

GET /api/system/{systemSlug}/api-audit-logs?page=1&pageSize=25
x-api-key: <system-api-key>

Path parameters

FieldTypeDetails
systemSlugstring · requiredSystem slug from /application/{systemSlug}/profile.

Query parameters

FieldTypeDetails
pageinteger · optional1–21,474,836; default 1. Clamped to the last page, or 1 when empty.
pageSizeinteger · optional1–100; default 25.
methodenum · optionalGET, POST, PUT, PATCH, DELETE, HEAD, or OPTIONS.
pathstring · optionalTrimmed, up to 255 characters; case-insensitive substring of the recorded route template.
fromDate / toDateYYYY-MM-DD · optionalValid calendar dates in the system’s time zone. Both boundary days are included; toDate cannot precede fromDate.
snapshotAtISO timestamp · optionalTimezone required. Reuse the returned value for subsequent pages. Future values are capped at the current time.

Request body: none.

Response · 200 OK

{
  "auditLogs": [
    {
      "id": "77777777-7777-4777-8777-777777777777",
      "systemId": "88888888-8888-4888-8888-888888888888",
      "createdAt": "2026-09-15T12:00:00.000Z",
      "userId": "44444444-4444-4444-8444-444444444444",
      "userName": "Alex River",
      "userEmail": "alex@example.com",
      "authenticationMethod": "api_key",
      "apiKeyId": "99999999-9999-4999-8999-999999999999",
      "method": "GET",
      "path": "/api/system/[systemSlug]/customer-account",
      "statusCode": 200,
      "durationMs": 24,
      "inputParameters": {
        "route": {
          "systemSlug": "north-buffalo"
        },
        "query": {
          "page": [
            "1"
          ],
          "pageSize": [
            "25"
          ]
        },
        "body": null
      }
    }
  ],
  "pagination": {
    "page": 1,
    "pageSize": 25,
    "totalItems": 1,
    "totalPages": 1,
    "hasPreviousPage": false,
    "hasNextPage": false
  },
  "snapshotAt": "2026-09-15T12:01:00.000Z"
}
FieldTypeDetails
auditLogsApiAuditLog[]Newest first by createdAt and id; strictly before snapshotAt.
paginationPaginationPage metadata.
snapshotAtISO timestampUpper boundary for this result window.

Response header: Cache-Control: no-store. System calls include failures and audit reads. Sensitive inputs are redacted; unverified/wrong-system callers have null identity and omitted input values. Headers and response bodies are not captured. Oversized, malformed, or unsupported bodies use omission markers.

Global/counter routes and framework-generated 404/405 responses are outside system audit coverage. Persistence is best effort during database/process failures.

See response object fields and error responses.

Response object fields

CustomerAccount

Money values are signed dollars, not integer cents. unpaidBalance is current; lifetime return totals are separate and must not be used to reconstruct it. There is no separate GET account, balance, or account-users endpoint: locate accounts in collection/search results.

FieldTypeDetails
idUUIDAccount identifier.
namestringAccount name.
address / payeestring | nullOptional saved text.
hasAchAccountNumber / hasAchRoutingNumberboolean · optionalSaved-state indicators, never bank values/ciphertext. Included in owner collection/search/create/update/deactivate responses; omitted from payout responses and staff search.
activebooleanAccount status.
unpaidBalancenumberCurrent signed dollar balance; may be negative. Account-paid transactions/reversals affect it and payouts reduce it.
totalReturnedContainersnumberLifetime containers attributed to the returning account.
totalReturnedValuenumberLifetime return value in dollars. Returns may credit a different fundraising account.
userCountintegerNumber of returned assignments.
usersAccountUser[]Assigned Customer memberships, including inactive memberships; active first, then fullName.

AccountUser

Account assignment, system membership, and global user IDs are distinct. Connecticut identity fields are null when reporting is disabled.

FieldTypeDetails
accountUserIdUUIDAccount assignment; use in PUT/DELETE user paths.
customerAccountIdUUIDOwning account.
membershipIdUUIDSystem membership.
userIdUUIDGlobal user.
emailstring | nullSaved global email.
firstName / lastNamestringSystem membership names.
fullNamestring | nullComputed display name.
cellphonestring | nullSaved global phone formatted as (202)-555-0123.
activebooleanSystem membership status, not account status.
customerAccountRoleOwner | Admin | UserRole on this account.
licensePlateNumber / driversLicenseNumberstring | nullIdentity fields, visible only for Connecticut-enabled systems.
activeIdDocumentIdDocument | nullActive identity-document metadata for Connecticut-enabled systems.

CustomerMember

Owner customer-provisioning response. It is a system membership rather than an account assignment.

FieldTypeDetails
membershipId / userIdUUIDSystem membership id and global user id.
email / cellphonestring | nullSaved global contacts; phone uses formatted display text.
firstName / lastNamestringSystem membership names.
fullNamestring | nullComputed display name.
activebooleantrue after successful creation/reactivation.
roleCustomerSystem role.
hourlyRatenumberStored membership rate.
posPinCode / notesstring | nullStored membership fields; a new Customer has no POS PIN.
licensePlateNumber / driversLicenseNumberstring | nullStored identity text when Connecticut reporting is enabled; otherwise null.
activeIdDocumentIdDocument | nullActive document metadata or null.

IdDocument

Metadata only; no file bytes or public download URL.

FieldTypeDetails
idUUIDDocument id.
createdAtISO timestampCreation instant.
fileNamestringOriginal file name.
contentTypestringFile MIME type.
fileSizeBytesnumberFile size in bytes.

Pagination

Shared pagination envelope; see each endpoint for out-of-range page behavior.

FieldTypeDetails
pageintegerReturned page number.
pageSizeintegerMaximum rows per page.
totalItemsintegerTotal matching records.
totalPagesintegerCeiling of totalItems/pageSize; 0 for no records.
hasPreviousPage / hasNextPagebooleanWhether an earlier/later page is available according to the endpoint’s pagination rules.

AccountTransaction

Includes paid-to and returned-by relationships. isPaidToLinkedAccount and isReturnedByLinkedAccount refer to the account requested in the URL. Monetary fields are dollars.

FieldTypeDetails
idUUIDTransaction id.
createdAtISO timestampCreation instant.
storeNamestring | nullStore display name.
grandTotalnumberRecorded final signed transaction total.
paidSubTotalnumberRecorded paid subtotal.
salesSubTotalnumberSales subtotal.
taxesSubTotalnumberTax total.
containerDepositsSubTotalnumberContainer deposit total.
roundingAdjustmentTotalnumberRecorded rounding adjustment.
totalReturnedContainersnumberReturned container count for the transaction.
paymentMethodCash | Customer Account | Credit CardNormalized payment method.
isPaidToCustomerAccountbooleanWhether account tender was used.
paidToCustomerAccountId / returnedByCustomerAccountIdUUID | nullReceiving account and account associated with the return.
paidToCustomerAccountName / returnedByCustomerAccountNamestring | nullCorresponding account names.
fundraiserIdUUID | nullRelated fundraiser.
fundraiserNamestring | nullFundraiser name.
isTransactionRefundedbooleanWhether this transaction has been reversed.
isPaidToLinkedAccount / isReturnedByLinkedAccountbooleanWhether each relationship matches the requested account.

AccountPayout

Adjustment history distinguishes normal Payout records from Correction records.

FieldTypeDetails
id / customerAccountIdUUIDAdjustment id and owning account id.
adjustmentTypePayout | CorrectionAdjustment category.
totalPayoutAmountnumberRecorded amount in dollars.
payoutMethodCheck | CashRecorded tender.
checkNumber / notesstring | nullOptional saved text.
createdAt / updatedAtISO timestampCreation and last-update instants.

ApiAuditLog

Sanitized operational request history. Input capture is bounded and may contain redaction, truncation, or omission markers.

FieldTypeDetails
id / systemIdUUIDAudit record and owning system.
createdAtISO timestampRecorded at request completion.
userIdUUID | nullVerified caller; null when unverified for this system.
userName / userEmailstring | nullVerified identity snapshot.
authenticationMethodsession | api_key | management | unauthenticatedVerified authentication mechanism.
apiKeyIdUUID | nullAPI-key metadata id, never the secret.
method / pathstringHTTP method and normalized route template.
statusCode / durationMsintegerResponse status and processing duration in milliseconds.
inputParametersobjectroute, query, and body contain sanitized JSON or omission markers; repeated query values are arrays.

Error responses

Check the HTTP status before reading success fields. The examples use fictional data. JSON responses can contain null fields; preserve null rather than treating it as an empty string.

FieldTypeDetails
400Bad requestInvalid fields/UUIDs, missing account or assignment, duplicate names/assignments, contact conflicts, or failed membership/payout preconditions.
401UnauthorizedInvalid, expired, revoked or wrong-system key; inactive membership; insufficient system role; or a write with Read permission.
500Server errorUnexpected failure, including malformed JSON. An uncertain write may have completed; reconcile before retrying.

Ordinary error · 400

{
  "error": "Customer account not found."
}

Validation error · 400

{
  "error": "ValidationError",
  "issues": [
    {
      "expected": "string",
      "code": "invalid_type",
      "path": [
        "name"
      ],
      "message": "Invalid input: expected string, received undefined"
    }
  ]
}

error is a string. Validation responses also contain issues, an array with code, path (field names or array indices), and message; other issue fields depend on the validation rule. Missing records use 400 and authorization failures use 401.