Direct Debits

Initiate automated bank debits to collect premiums directly from an insured's bank account. Supports individual debits and batch collections across multiple policies.


Initiate a Direct Debit #

POST /api/v1/ach/debits
Field Type Required Description
employer_id string Yes Employer ObjectId
carrier_id string Yes Carrier ObjectId
policy_id string Yes Policy ObjectId
bank_account_id string Yes Bank account ID to debit from
amount_cents integer Yes Amount in cents
payment_purpose string Yes See payment purposes below
description string No Human-readable description
idempotency_key string No Unique key to prevent duplicate charges
effective_date string No Desired effective date (ISO 8601)
metadata object No Custom key-value pairs

Payment Purposes #

Purpose Description
paygo Pay-as-you-go premium collection
down_payment Initial policy down payment
installment Scheduled installment payment
late_payment Late payment collection
extra_payment Extra/overpayment
overdue_payment Overdue amount
curl -X POST /api/v1/ach/debits \
  -H "X-Client-ID: $CLIENT_ID" \
  -H "X-Client-Secret: $CLIENT_SECRET" \
  -d '{
    "employer_id": "681xyz789abc123456789012",
    "carrier_id": "680abc456def789012345678",
    "policy_id": "682def789ghi012345678901",
    "bank_account_id": "684abc123def456789012345",
    "amount_cents": 85000,
    "payment_purpose": "paygo",
    "description": "PayGo premium - March 2026",
    "idempotency_key": "paygo_march_2026_acme"
  }'

Response (201 Created)

{
  "ok": true,
  "data": {
    "success": true,
    "transaction_id": "685def456ghi789012345678",
    "status": "PENDING",
    "amount": 85000
  }
}

Idempotency

: Always provide an idempotency_key to prevent duplicate charges. If you retry a request with the same key, the original transaction is returned instead of creating a new one.


Check Debit Status #

GET /api/v1/ach/debits/{transaction_id}/status

Transaction Statuses #

Audit1's ACH ledger (Ach_activity.status) carries two parallel vocabularies depending on the row's origin. New debits initiated through this API land in the premium_calculation vocabulary. Rows you may retrieve via reporting endpoints can also be in the legacy vocabulary (sourced from the legacy Audit1/Cennairus pipelines and the ACH Processing Company portal).

Group your filtering by what the money is doing, not by the literal status string.

Group Statuses (premium_calculation) Statuses (legacy) Meaning
In flight pending, Scheduled pending, at_sb, file_transmitted Created or queued; on its way to the bank
Money landed submitted, processed (and a forward-compat settled) sb_remit, at_sb, file_transmitted, legacy_collected Funds have reached the bank or service bureau
Failed cancelled, failed, quarantined returned, failed, canceled (one l) Money did not move; see failure_reason

Info

The legacy sb_remit status alone covers 94K+ rows in production — filtering by only the new vocabulary will hide ~99% of historical money. Always group by the table above rather than matching individual strings.

After the bank acknowledges the file, the transaction's status flips to submitted or sb_remit (depending on source). Returns later flip it to returned/failed via the return-handler service.


Server-Side Gates #

Audit1 enforces four server-side gates before a debit reaches the bank. A debit that fails a gate is never submitted; instead it lands in one of the failure-status groups above with a machine-readable reason. None of these gates are caller-controllable — they are platform-level invariants.

1. Billing Eligibility Gate #

The policy's billing mode must allow ACH auto-debit at the moment Phase 3 runs.

reason When it fires
non_paygo_blocked The policy's billing_type is set and is not PAY_AS_YOU_GO (e.g., the carrier flipped it to DIRECT_BILLING, AUDIT, or TRADITIONAL). The carrier bills the employer directly — Audit1 must never auto-debit.
policy_inactive_blocked The policy is cancelled (hard block) or expired with a file check_date on/after expiration. Files dated before expiration are still collectible.

This is the last line of defense: it runs at Phase 3 time, so it catches policies that flipped state after the payroll file was ingested but before the debit was created.

2. Legacy Dedup Gate #

Phase 3 checks the legacy Azure SQL Payment table before flipping any debit to submitted. If the same (employer, period, premium) was already collected by the legacy ACH service, the new debit is suppressed with reason = "legacy_collected". This prevents the new pipeline and the legacy pipeline from both pulling the same money during the cutover transition.

3. Forward-Only Cutoff #

Debits whose ingestion timestamp falls before ACH_FORWARD_ONLY_CUTOFF (default 2026-04-27) are treated as belonging to the legacy pipeline and are not collected by the new ACH gateway. Historical files re-ingested after the cutoff are also blocked under this rule. The cutoff is platform-wide; override is operational only (not exposed to API callers).

4. Anomaly Rail #

Each batch run checks every entry against the employer's recent ACH amount average. If a single entry's amount_cents is more than 100% above the recent average for that employer, the entry is quarantined with reason = "amount_anomaly_above_100pct_of_recent_average". Operations resolves the quarantine manually; the caller sees the transaction stay in quarantined until that happens.

None of these gates produce a 4xx response on the original POST.

They run asynchronously inside the batch worker. To detect a gated debit, poll GET /ach/debits/{transaction_id}/status and inspect both the status group and the structured reason field.



Batch Collection #

Collect from a single employer across multiple policies in one batch.

POST /api/v1/ach/collections
Field Type Required Description
employerId string Yes Employer to debit
bankAccountId string Yes Bank account ID
policyCollections array Yes One entry per policy
policyCollections[].policyId string Yes Policy ObjectId
policyCollections[].totalInDollars number Yes Amount in dollars
policyCollections[].description string Yes Line-item description
policyCollections[].idempotencyKey string Yes Unique per collection
curl -X POST /api/v1/ach/collections \
  -H "X-Client-ID: $CLIENT_ID" \
  -H "X-Client-Secret: $CLIENT_SECRET" \
  -d '{
    "employerId": "681xyz789abc123456789012",
    "bankAccountId": "684abc123def456789012345",
    "policyCollections": [
      {
        "policyId": "682def789ghi012345678901",
        "totalInDollars": 1500.00,
        "description": "WC Premium - Q1 2026",
        "idempotencyKey": "wc_q1_2026_acme"
      },
      {
        "policyId": "682ghi012jkl345678901234",
        "totalInDollars": 850.00,
        "description": "GL Premium - Q1 2026",
        "idempotencyKey": "gl_q1_2026_acme"
      }
    ]
  }'

Response (202 Accepted) — batch processing is asynchronous.


List Transactions #

GET /api/v1/ach/transactions?employer_id=...&status=COMPLETED&from_date=2026-01-01&page=1&limit=50
Parameter Type Description
carrier_id string Filter by carrier
employer_id string Filter by employer
policy_id string Filter by policy
status string PENDING, COMPLETED, RETURNED, etc.
from_date string Start date (ISO 8601)
to_date string End date
sort_by string Field to sort by
sort_order string asc or desc

Transaction History #

GET /api/v1/ach/transactions/{id}/history

Returns the audit trail for a specific transaction.

Transaction Fees #

GET /api/v1/ach/transactions/{id}/fees

Returns fee breakdown for a transaction.


Bank debit settlement typically takes 2-5 business days.

The COMPLETED status means funds have been received. Use webhooks to get notified in real time.