Bank Accounts & Linking

Connect insured bank accounts for direct debits. The system uses a two-layer model:

  • Bank Accounts -- High-level account metadata (name, bank name, connection type, status). Used for display and association.
  • ACH Accounts -- Encrypted credential layer (routing number, account number, encrypted at rest with AES-256-GCM). Used for actual money movement.

Use the Plaid bank linking widget for instant, verified bank connections or add accounts manually.

Base URL: https://payments.audit1.com/api/v1 — every endpoint on this page lives there. This is a different host from the core Developer API (https://apiv2.audit1.com/api/v2, used for payroll reports, employee sync, and API keys) — that host does not serve payment endpoints.


Embed Plaid's bank linking widget in your UI. The insured selects their bank, authenticates, and you get verified account details -- no manual entry needed. Supports 10,000+ financial institutions.

POST /api/v1/plaid/link-token
Field Type Required Description
user_id string Yes Your internal user identifier
curl -X POST https://payments.audit1.com/api/v1/plaid/link-token \
  -H "X-Client-ID: $CLIENT_ID" \
  -H "X-Client-Secret: $CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "user_id": "your_internal_user_id" }'
{
  "ok": true,
  "data": {
    "link_token": "link-sandbox-12345-abcde..."
  }
}

Step 2: Open the Bank Linking Widget in Your Frontend #

// Install: npm install react-plaid-link
import { usePlaidLink } from "react-plaid-link";

const { open } = usePlaidLink({
  token: linkToken,  // from Step 1
  onSuccess: (publicToken, metadata) => {
    // Send publicToken to your backend
    exchangeToken(publicToken, metadata);
  },
});

open();

See the Plaid Link documentation for full frontend integration details and supported frameworks.

Step 3: Exchange the Public Token #

POST /api/v1/plaid/exchange
Field Type Required Description
public_token string Yes Public token from Plaid Link onSuccess callback
user_id string Yes Your internal user identifier
employer_id string No Employer ObjectId to associate the connection with
carrier_id string No Carrier ObjectId (if linking for a carrier)
curl -X POST https://payments.audit1.com/api/v1/plaid/exchange \
  -H "X-Client-ID: $CLIENT_ID" \
  -H "X-Client-Secret: $CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "public_token": "public-sandbox-12345-abcde...",
    "user_id": "your_internal_user_id",
    "employer_id": "681xyz789abc123456789012"
  }'

This creates a permanent Plaid connection and automatically syncs the insured's bank accounts.

Step 4: Create an ACH Account from the Linked Bank #

POST /api/v1/bank-accounts/ach/from-plaid
Field Type Required Description
owner_type string Yes employer or carrier
owner_id string Yes Entity ObjectId
plaid_account_id string Yes Plaid account ID from the connection
plaid_item_id string Yes Plaid item ID from the connection
access_token string Yes Plaid access token (from exchange step)
nickname string No Friendly name for the account
is_primary boolean No Set as default account
curl -X POST https://payments.audit1.com/api/v1/bank-accounts/ach/from-plaid \
  -H "X-Client-ID: $CLIENT_ID" \
  -H "X-Client-Secret: $CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "owner_type": "employer",
    "owner_id": "681xyz789abc123456789012",
    "plaid_account_id": "acct_123...",
    "plaid_item_id": "item_456...",
    "access_token": "access-sandbox-abc123...",
    "nickname": "Operating Account",
    "is_primary": true
  }'

The ACH account is pre-verified (no micro-deposits needed) because credentials come directly from the linked bank via Plaid.


Manual Bank Account Entry #

For insureds who prefer not to use the bank linking widget.

Create a Bank Account (Metadata Layer) #

POST /api/v1/bank-accounts
Field Type Required Description
user_id string Yes User who owns or manages this account
name string Yes Account display name
bank_name string Yes Bank institution name
employer_id string No Employer ObjectId to associate with
carrier_id string No Carrier ObjectId to associate with
connection_type string No manual or plaid
primary boolean No Set as default account
curl -X POST https://payments.audit1.com/api/v1/bank-accounts \
  -H "X-Client-ID: $CLIENT_ID" \
  -H "X-Client-Secret: $CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "user_abc123",
    "name": "Payroll Account",
    "bank_name": "Chase",
    "employer_id": "681xyz789abc123456789012"
  }'

Create an ACH Account (Credential Layer) #

POST /api/v1/bank-accounts/ach
Field Type Required Description
owner_type string Yes employer or carrier
owner_id string Yes Entity ObjectId
routing_number string Yes 9-digit routing number
account_number string Yes Bank account number (5-17 digits)
bank_name string Yes Bank institution name
account_type string Yes checking or savings
nickname string No Friendly name
is_primary boolean No Set as default account
curl -X POST https://payments.audit1.com/api/v1/bank-accounts/ach \
  -H "X-Client-ID: $CLIENT_ID" \
  -H "X-Client-Secret: $CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "owner_type": "employer",
    "owner_id": "681xyz789abc123456789012",
    "routing_number": "021000021",
    "account_number": "1234567890",
    "bank_name": "Chase",
    "account_type": "checking",
    "nickname": "Payroll Account"
  }'
{
  "ok": true,
  "data": {
    "_id": "684abc123def456789012345",
    "account_last4": "7890",
    "routing_last4": "0021",
    "status": "unverified",
    "bank_name": "Chase",
    "account_type": "checking"
  }
}

Note: Manually-entered ACH accounts start as unverified. Full routing and account numbers are never returned in API responses -- only the last 4 digits are shown.


Bank Account Management (Metadata Layer) #

List Bank Accounts #

GET /api/v1/bank-accounts?employer_id=...&carrier_id=...&connection_type=plaid&status=active&primary=true&page=1&limit=50

Get a Bank Account #

GET /api/v1/bank-accounts/{id}

Update a Bank Account #

PATCH /api/v1/bank-accounts/{id}

Set as Primary Account #

PATCH /api/v1/bank-accounts/{id}/default
{ "primary": true }

Delete a Bank Account #

DELETE /api/v1/bank-accounts/{id}

ACH Account Management (Credential Layer) #

List ACH Accounts #

GET /api/v1/bank-accounts/ach?owner_id=...&owner_type=employer&status=active&connection_type=plaid

Get an ACH Account #

GET /api/v1/bank-accounts/ach/{id}

Update an ACH Account #

PATCH /api/v1/bank-accounts/ach/{id}

Delete an ACH Account #

DELETE /api/v1/bank-accounts/ach/{id}

Plaid Connections & Transactions #

List Connections #

GET /api/v1/plaid/connections?employer_id=...&carrier_id=...&status=active&page=1&limit=50

Get a Connection #

GET /api/v1/plaid/connections/{id}

Update a Connection (Add Products) #

POST /api/v1/plaid/link-token/update
Field Type Required Description
user_id string Yes Your internal user identifier
item_id string Yes Plaid item ID to update

Sync Accounts from Connection #

POST /api/v1/plaid/sync-accounts
Field Type Required Description
item_id string Yes Plaid item ID to sync

Get Live Balances #

POST /api/v1/plaid/balance
Field Type Required Description
plaid_item_id string Yes Plaid item ID

View Bank Transactions #

GET /api/v1/plaid/transactions?employer_id=...&plaid_item_id=...&plaid_account_id=...&date_from=2026-01-01&date_to=2026-03-31&pending=false&page=1&limit=50

Get a Transaction #

GET /api/v1/plaid/transactions/{id}

Transaction Summary #

GET /api/v1/plaid/transactions/summary?employer_id=...&plaid_item_id=...&date_from=2026-01-01&date_to=2026-03-31
{
  "ok": true,
  "data": {
    "total_transactions": 847,
    "total_debits_cents": 12500000,
    "total_credits_cents": 15200000,
    "net_cents": 2700000
  }
}

Security #

  • Routing and account numbers are encrypted at rest using envelope encryption (AES-256-GCM)
  • Credentials are never returned in API responses -- only the last 4 digits are shown
  • All bank operations are logged without sensitive data
  • Plaid connection tokens are encrypted before storage
  • All API requests require tenant-scoped authentication -- you can only access accounts belonging to employers/carriers within your scope